| @@ -73,6 +73,10 @@ ALL_LIBS += $(MODULEDIR)/rtaudio.a | |||
| ALL_LIBS += $(MODULEDIR)/rtmidi.a | |||
| endif | |||
| ifeq ($(HAVE_ZYN_DEPS),true) | |||
| ALL_LIBS += $(MODULEDIR)/rtosc.a | |||
| endif | |||
| ifeq ($(HAVE_QT4),true) | |||
| ALL_LIBS += $(MODULEDIR)/theme.qt4.a | |||
| endif | |||
| @@ -534,9 +538,13 @@ endif | |||
| bin/resources/nekofilter/*.png \ | |||
| $(DESTDIR)$(PREFIX)/share/carla/resources/nekofilter/ | |||
| ifeq ($(HAVE_ZYN_DEPS),true) | |||
| ifeq ($(HAVE_ZYN_UI_DEPS),true) | |||
| install -m 644 \ | |||
| bin/resources/zynaddsubfx/*.png \ | |||
| $(DESTDIR)$(PREFIX)/share/carla/resources/zynaddsubfx/ | |||
| endif | |||
| endif | |||
| # Install resources (re-use python files) | |||
| $(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 | |||
| endif | |||
| # TESTING | |||
| HAVE_ZYN_UI_DEPS = false | |||
| ifeq ($(HAVE_DGL),true) | |||
| NATIVE_PLUGINS_LIBS += $(DGL_LIBS) | |||
| ifeq ($(HAVE_PROJECTM),true) | |||
| @@ -429,7 +432,7 @@ endif | |||
| ifeq ($(EXPERIMENTAL_PLUGINS),true) | |||
| BASE_FLAGS += -DHAVE_EXPERIMENTAL_PLUGINS | |||
| 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 | |||
| ifeq ($(HAVE_ZYN_DEPS),true) | |||
| @@ -52,6 +52,10 @@ STANDALONE_LIBS += $(MODULEDIR)/rtaudio.a | |||
| STANDALONE_LIBS += $(MODULEDIR)/rtmidi.a | |||
| endif | |||
| ifeq ($(HAVE_ZYN_DEPS),true) | |||
| STANDALONE_LIBS += $(MODULEDIR)/rtosc.a | |||
| endif | |||
| UTILS_LIBS = $(MODULEDIR)/juce_audio_basics.a | |||
| UTILS_LIBS += $(MODULEDIR)/juce_audio_formats.a | |||
| UTILS_LIBS += $(MODULEDIR)/juce_core.a | |||
| @@ -114,6 +114,10 @@ LINK_FLAGS += $(JUCE_GUI_EXTRA_LIBS) | |||
| endif | |||
| endif | |||
| ifeq ($(HAVE_ZYN_DEPS),true) | |||
| LIBS_native += $(MODULEDIR)/rtosc.a | |||
| endif | |||
| LINK_FLAGS += $(LIBLO_LIBS) | |||
| 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) | |||
| ZYN_CXX_FLAGS = $(BUILD_CXX_FLAGS) -isystem zynaddsubfx | |||
| 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_NTK),true) | |||
| FLUID = ntk-fluid | |||
| ZYN_CXX_FLAGS += -DNTK_GUI | |||
| ZYN_CXX_FLAGS += $(shell pkg-config --cflags ntk_images ntk) | |||
| else | |||
| else # HAVE_NTK | |||
| FLUID = fluid | |||
| ZYN_CXX_FLAGS += -DFLTK_GUI | |||
| 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 | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * 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 | |||
| * modify it under the terms of the GNU General Public License as | |||
| @@ -25,13 +25,16 @@ | |||
| #include "Effects/Echo.h" | |||
| #include "Effects/Phaser.h" | |||
| #include "Effects/Reverb.h" | |||
| #include "Misc/Allocator.h" | |||
| #include "juce_audio_basics.h" | |||
| using juce::roundToIntAccurate; | |||
| using juce::FloatVectorOperations; | |||
| using juce::SharedResourcePointer; | |||
| // ----------------------------------------------------------------------- | |||
| template<class ZynFX> | |||
| class FxAbstractPlugin : public NativePluginClass | |||
| { | |||
| protected: | |||
| @@ -39,18 +42,21 @@ protected: | |||
| : NativePluginClass(host), | |||
| fParamCount(paramCount-2), // volume and pan handled by host | |||
| fProgramCount(programCount), | |||
| fBufferSize(getBufferSize()), | |||
| fSampleRate(getSampleRate()), | |||
| fEffect(nullptr), | |||
| efxoutl(nullptr), | |||
| efxoutr(nullptr), | |||
| 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 | |||
| @@ -147,7 +153,11 @@ protected: | |||
| 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[] efxoutr; | |||
| @@ -156,52 +166,74 @@ protected: | |||
| FloatVectorOperations::clear(efxoutl, ibufferSize); | |||
| FloatVectorOperations::clear(efxoutr, ibufferSize); | |||
| doReinit(getSampleRate(), bufferSize); | |||
| doReinit(false); | |||
| } | |||
| 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]; | |||
| 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 fProgramCount; | |||
| uint32_t fBufferSize; | |||
| double fSampleRate; | |||
| Effect* fEffect; | |||
| float* efxoutl; | |||
| float* efxoutr; | |||
| SharedResourcePointer<Allocator> fAllocator; | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxAbstractPlugin) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| class FxAlienWahPlugin : public FxAbstractPlugin | |||
| class FxAlienWahPlugin : public FxAbstractPlugin<Alienwah> | |||
| { | |||
| public: | |||
| FxAlienWahPlugin(const NativeHostDescriptor* const host) | |||
| : FxAbstractPlugin(host, 11, 4), | |||
| leakDetector_FxAlienWahPlugin() | |||
| { | |||
| fEffect = new Alienwah(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||
| } | |||
| leakDetector_FxAlienWahPlugin() {} | |||
| 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) | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxAlienWahPlugin) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| class FxChorusPlugin : public FxAbstractPlugin | |||
| class FxChorusPlugin : public FxAbstractPlugin<Chorus> | |||
| { | |||
| public: | |||
| FxChorusPlugin(const NativeHostDescriptor* const host) | |||
| : FxAbstractPlugin(host, 12, 10), | |||
| leakDetector_FxChorusPlugin() | |||
| { | |||
| fEffect = new Chorus(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||
| } | |||
| leakDetector_FxChorusPlugin() {} | |||
| 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) | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxChorusPlugin) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| class FxDistortionPlugin : public FxAbstractPlugin | |||
| class FxDistortionPlugin : public FxAbstractPlugin<Distorsion> | |||
| { | |||
| public: | |||
| FxDistortionPlugin(const NativeHostDescriptor* const host) | |||
| : FxAbstractPlugin(host, 11, 6), | |||
| leakDetector_FxDistortionPlugin() | |||
| { | |||
| fEffect = new Distorsion(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||
| } | |||
| leakDetector_FxDistortionPlugin() {} | |||
| 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) | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxDistortionPlugin) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| class FxDynamicFilterPlugin : public FxAbstractPlugin | |||
| class FxDynamicFilterPlugin : public FxAbstractPlugin<DynamicFilter> | |||
| { | |||
| public: | |||
| FxDynamicFilterPlugin(const NativeHostDescriptor* const host) | |||
| : FxAbstractPlugin(host, 10, 5), | |||
| leakDetector_FxDynamicFilterPlugin() | |||
| { | |||
| fEffect = new DynamicFilter(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||
| } | |||
| leakDetector_FxDynamicFilterPlugin() {} | |||
| 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) | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxDynamicFilterPlugin) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| class FxEchoPlugin : public FxAbstractPlugin | |||
| class FxEchoPlugin : public FxAbstractPlugin<Echo> | |||
| { | |||
| public: | |||
| FxEchoPlugin(const NativeHostDescriptor* const host) | |||
| : FxAbstractPlugin(host, 7, 9), | |||
| leakDetector_FxEchoPlugin() | |||
| { | |||
| fEffect = new Echo(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||
| } | |||
| leakDetector_FxEchoPlugin() {} | |||
| 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) | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxEchoPlugin) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| class FxPhaserPlugin : public FxAbstractPlugin | |||
| class FxPhaserPlugin : public FxAbstractPlugin<Phaser> | |||
| { | |||
| public: | |||
| FxPhaserPlugin(const NativeHostDescriptor* const host) | |||
| : FxAbstractPlugin(host, 15, 12), | |||
| leakDetector_FxPhaserPlugin() | |||
| { | |||
| fEffect = new Phaser(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||
| } | |||
| leakDetector_FxPhaserPlugin() {} | |||
| 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) | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxPhaserPlugin) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| class FxReverbPlugin : public FxAbstractPlugin | |||
| class FxReverbPlugin : public FxAbstractPlugin<Reverb> | |||
| { | |||
| public: | |||
| FxReverbPlugin(const NativeHostDescriptor* const host) | |||
| : FxAbstractPlugin(host, 13, 13), | |||
| leakDetector_FxReverbPlugin() | |||
| { | |||
| fEffect = new Reverb(false, efxoutl, efxoutr, static_cast<uint>(getSampleRate()), static_cast<int>(getBufferSize())); | |||
| } | |||
| leakDetector_FxReverbPlugin() {} | |||
| 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) | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxReverbPlugin) | |||
| }; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * 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 | |||
| * modify it under the terms of the GNU General Public License as | |||
| @@ -22,6 +22,13 @@ | |||
| #define warnx(...) | |||
| #endif | |||
| #define PLUGINVERSION | |||
| #include "zynaddsubfx/tlsf/tlsf.h" | |||
| extern "C" { | |||
| #include "zynaddsubfx/tlsf/tlsf.c" | |||
| } | |||
| // zynaddsubfx includes | |||
| #include "zynaddsubfx/DSP/AnalogFilter.cpp" | |||
| #include "zynaddsubfx/DSP/FFTwrapper.cpp" | |||
| @@ -40,12 +47,14 @@ | |||
| #include "zynaddsubfx/Effects/EQ.cpp" | |||
| #include "zynaddsubfx/Effects/Phaser.cpp" | |||
| #include "zynaddsubfx/Effects/Reverb.cpp" | |||
| #include "zynaddsubfx/Misc/Allocator.cpp" | |||
| #include "zynaddsubfx/Misc/Bank.cpp" | |||
| #include "zynaddsubfx/Misc/Config.cpp" | |||
| #include "zynaddsubfx/Misc/Dump.cpp" | |||
| #include "zynaddsubfx/Misc/Master.cpp" | |||
| #include "zynaddsubfx/Misc/Microtonal.cpp" | |||
| #include "zynaddsubfx/Misc/MiddleWare.cpp" | |||
| #include "zynaddsubfx/Misc/Part.cpp" | |||
| #include "zynaddsubfx/Misc/PresetExtractor.cpp" | |||
| #include "zynaddsubfx/Misc/Recorder.cpp" | |||
| //#include "zynaddsubfx/Misc/Stereo.cpp" | |||
| #include "zynaddsubfx/Misc/Util.cpp" | |||
| @@ -71,21 +80,25 @@ | |||
| #include "zynaddsubfx/Synth/SUBnote.cpp" | |||
| #include "zynaddsubfx/Synth/SynthNote.cpp" | |||
| #ifdef NO_UI | |||
| #include "zynaddsubfx/UI/ConnectionDummy.cpp" | |||
| #endif | |||
| // 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; | |||
| 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 waveStart(void){} | |||
| void waveStop(void){} | |||
| void waveEnd(void){} | |||
| void waveStart(){} | |||
| void waveStop(){} | |||
| // void waveEnd(void){} | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * 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 | |||
| * modify it under the terms of the GNU General Public License as | |||
| @@ -111,12 +111,10 @@ public: | |||
| return; | |||
| fInitiated = true; | |||
| Master& master(Master::getInstance()); | |||
| pthread_mutex_lock(&master.mutex); | |||
| fPrograms.append(new ProgramInfo(0, 0, "default")); | |||
| Master& master(getMasterInstance()); | |||
| // refresh banks | |||
| master.bank.rescanforbanks(); | |||
| @@ -137,48 +135,43 @@ public: | |||
| 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) | |||
| { | |||
| pthread_mutex_lock(&master->mutex); | |||
| if (program != 0) | |||
| return; | |||
| const CarlaMutexLocker cml(mutex); | |||
| master->partonoff(channel, 1); | |||
| master->part[channel]->defaults(); | |||
| master->part[channel]->applyparameters(false); | |||
| pthread_mutex_unlock(&master->mutex); | |||
| 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()); | |||
| } | |||
| const NativeMidiProgram* getInfo(const uint32_t index) const noexcept | |||
| const NativeMidiProgram* getNativeMidiProgramInfo(const uint32_t index) const noexcept | |||
| { | |||
| if (index >= fPrograms.count()) | |||
| return nullptr; | |||
| @@ -193,6 +186,13 @@ public: | |||
| return &fRetProgram; | |||
| } | |||
| uint32_t getZynBankCount() const | |||
| { | |||
| const Master& master(getMasterInstance()); | |||
| return master.bank.banks.size(); | |||
| } | |||
| private: | |||
| struct ProgramInfo { | |||
| uint32_t bank; | |||
| @@ -226,140 +226,28 @@ private: | |||
| mutable NativeMidiProgram fRetProgram; | |||
| 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_DECLARE_NON_COPY_CLASS(ZynAddSubFxInstanceCount) | |||
| CARLA_DECLARE_NON_COPY_CLASS(ZynAddSubFxPrograms) | |||
| }; | |||
| static ZynAddSubFxInstanceCount sInstanceCount; | |||
| static ZynAddSubFxPrograms sPrograms; | |||
| // ----------------------------------------------------------------------- | |||
| class ZynAddSubFxThread : public CarlaThread | |||
| { | |||
| public: | |||
| ZynAddSubFxThread(Master* const master, const NativeHostDescriptor* const host) | |||
| ZynAddSubFxThread(Master* const master, CarlaMutex& mutex, const NativeHostDescriptor* const host) | |||
| : CarlaThread("ZynAddSubFxThread"), | |||
| fMaster(master), | |||
| fMutex(mutex), | |||
| kHost(host), | |||
| #ifdef WANT_ZYNADDSUBFX_UI | |||
| fUi(nullptr), | |||
| @@ -475,12 +363,11 @@ protected: | |||
| if (fUi == nullptr) | |||
| { | |||
| fUiClosed = 0; | |||
| fUi = new MasterUI(fMaster, &fUiClosed); | |||
| fUi = new MasterUI(&fUiClosed, &fOscIface); | |||
| fUi->masterwindow->label(kHost->uiName); | |||
| fUi->showUI(); | |||
| } | |||
| else | |||
| fUi->showUI(); | |||
| fUi->showUI(1); | |||
| } | |||
| else if (fNextUiAction == 0) // close | |||
| { | |||
| @@ -509,7 +396,7 @@ protected: | |||
| if (fChangeProgram) | |||
| { | |||
| fChangeProgram = false; | |||
| sPrograms.load(fMaster, fNextChannel, fNextBank, fNextProgram); | |||
| sPrograms.load(fMaster, fMutex, fNextChannel, fNextBank, fNextProgram); | |||
| fNextChannel = 0; | |||
| fNextBank = 0; | |||
| fNextProgram = 0; | |||
| @@ -545,12 +432,14 @@ protected: | |||
| private: | |||
| Master* fMaster; | |||
| CarlaMutex& fMutex; | |||
| const NativeHostDescriptor* const kHost; | |||
| #ifdef WANT_ZYNADDSUBFX_UI | |||
| MasterUI* fUi; | |||
| int fUiClosed; | |||
| volatile int fNextUiAction; | |||
| Fl_Osc_Interface fOscIface; | |||
| #endif | |||
| volatile bool fChangeProgram; | |||
| @@ -580,9 +469,10 @@ public: | |||
| ZynAddSubFxPlugin(const NativeHostDescriptor* const host) | |||
| : NativePluginClass(host), | |||
| fMaster(nullptr), | |||
| fSampleRate(static_cast<uint>(getSampleRate())), | |||
| fSynth(), | |||
| fIsActive(false), | |||
| fThread(nullptr, host), | |||
| fMutex(), | |||
| fThread(nullptr, fMutex, host), | |||
| leakDetector_ZynAddSubFxPlugin() | |||
| { | |||
| // init parameters to default | |||
| @@ -593,7 +483,19 @@ public: | |||
| fParameters[kParamResCenter] = 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(); | |||
| sPrograms.initIfNeeded(); | |||
| } | |||
| @@ -668,14 +570,14 @@ protected: | |||
| // ------------------------------------------------------------------- | |||
| // 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 | |||
| { | |||
| if (bank >= fMaster->bank.banks.size()) | |||
| if (bank >= sPrograms.getZynBankCount()) | |||
| return; | |||
| if (program >= BANK_SIZE) | |||
| return; | |||
| if (isOffline() || ! fIsActive) | |||
| { | |||
| sPrograms.load(fMaster, channel, bank, program); | |||
| sPrograms.load(fMaster, fMutex, channel, bank, program); | |||
| #ifdef WANT_ZYNADDSUBFX_UI | |||
| fThread.uiRepaint(); | |||
| #endif | |||
| @@ -721,7 +623,7 @@ protected: | |||
| CARLA_SAFE_ASSERT_RETURN(key != nullptr,); | |||
| CARLA_SAFE_ASSERT_RETURN(value != nullptr,); | |||
| pthread_mutex_lock(&fMaster->mutex); | |||
| const CarlaMutexLocker cml(fMutex); | |||
| /**/ if (std::strcmp(key, "CarlaAlternateFile1") == 0) // xmz | |||
| { | |||
| @@ -734,9 +636,7 @@ protected: | |||
| 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 | |||
| { | |||
| 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) | |||
| @@ -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 | |||
| @@ -834,8 +736,6 @@ protected: | |||
| char* getState() const override | |||
| { | |||
| config.save(); | |||
| char* data = nullptr; | |||
| fMaster->getalldata(&data); | |||
| return data; | |||
| @@ -844,43 +744,53 @@ protected: | |||
| void setState(const char* const data) override | |||
| { | |||
| fThread.stopLoadProgramLater(); | |||
| const CarlaMutexLocker cml(fMutex); | |||
| fMaster->putalldata(const_cast<char*>(data), 0); | |||
| fMaster->applyparameters(true); | |||
| fMaster->applyparameters(); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // Plugin dispatcher | |||
| void bufferSizeChanged(const uint32_t) final | |||
| void bufferSizeChanged(const uint32_t bufferSize) final | |||
| { | |||
| char* const state(getState()); | |||
| _deleteMaster(); | |||
| sInstanceCount.maybeReinit(getHostHandle()); | |||
| fSynth.buffersize = static_cast<int>(bufferSize); | |||
| fSynth.alias(); | |||
| _initMaster(); | |||
| if (state != nullptr) | |||
| { | |||
| fMaster->putalldata(state, 0); | |||
| fMaster->applyparameters(true); | |||
| fMaster->applyparameters(); | |||
| std::free(state); | |||
| } | |||
| } | |||
| void sampleRateChanged(const double sampleRate) final | |||
| { | |||
| fSampleRate = static_cast<uint>(sampleRate); | |||
| char* const state(getState()); | |||
| _deleteMaster(); | |||
| sInstanceCount.maybeReinit(getHostHandle()); | |||
| fSynth.samplerate = static_cast<uint>(sampleRate); | |||
| fSynth.alias(); | |||
| // FIXME | |||
| fSynth.samplerate_f = sampleRate; | |||
| _initMaster(); | |||
| if (state != nullptr) | |||
| { | |||
| fMaster->putalldata(state, 0); | |||
| fMaster->applyparameters(true); | |||
| fMaster->applyparameters(); | |||
| std::free(state); | |||
| } | |||
| } | |||
| @@ -896,35 +806,13 @@ protected: | |||
| private: | |||
| Master* fMaster; | |||
| uint fSampleRate; | |||
| SYNTH_T fSynth; | |||
| bool fIsActive; | |||
| float fParameters[kParamCount]; | |||
| CarlaMutex fMutex; | |||
| 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) | |||
| { | |||
| switch (index) | |||
| @@ -952,7 +840,7 @@ private: | |||
| void _initMaster() | |||
| { | |||
| fMaster = new Master(); | |||
| fMaster = new Master(fSynth); | |||
| fThread.setMaster(fMaster); | |||
| fThread.startThread(); | |||
| @@ -971,8 +859,6 @@ private: | |||
| void _deleteMaster() | |||
| { | |||
| //ensure that everything has stopped | |||
| pthread_mutex_lock(&fMaster->mutex); | |||
| pthread_mutex_unlock(&fMaster->mutex); | |||
| fThread.stopThread(-1); | |||
| delete fMaster; | |||
| @@ -984,14 +870,33 @@ private: | |||
| public: | |||
| 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); | |||
| } | |||
| static void _cleanup(NativePluginHandle handle) | |||
| { | |||
| delete (ZynAddSubFxPlugin*)handle; | |||
| sInstanceCount.removeOne(); | |||
| } | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZynAddSubFxPlugin) | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * 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 | |||
| * modify it under the terms of the GNU General Public License as | |||
| @@ -24,21 +24,21 @@ | |||
| CarlaString gUiPixmapPath; | |||
| // 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 | |||
| if(!zerocoefs) { | |||
| tmpq = sqrtf(tmpq); | |||
| alpha = sn / (2.0f * tmpq); | |||
| beta = sqrtf(tmpgain) / tmpq; | |||
| tmp = (tmpgain + 1.0f) + (tmpgain - 1.0f) * cs + beta * sn; | |||
| @@ -239,7 +238,6 @@ void AnalogFilter::computefiltercoefs(void) | |||
| case 8: //High Shelf - 2 poles | |||
| if(!zerocoefs) { | |||
| tmpq = sqrtf(tmpq); | |||
| alpha = sn / (2.0f * tmpq); | |||
| beta = sqrtf(tmpgain) / tmpq; | |||
| 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 | |||
| struct Coeff { | |||
| float c[3], //Feed Forward | |||
| d[3]; //Feed Back | |||
| } coeff, oldCoeff; | |||
| private: | |||
| struct fstage { | |||
| float x1, x2; //Input History | |||
| float y1, y2; //Output History | |||
| } 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 | |||
| //Apply IIR filter to Samples, with coefficients, and past history | |||
| @@ -23,13 +23,24 @@ | |||
| #include <cmath> | |||
| #include <cassert> | |||
| #include <cstring> | |||
| #include <pthread.h> | |||
| #include "FFTwrapper.h" | |||
| static pthread_mutex_t *mutex = NULL; | |||
| 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_; | |||
| time = new fftw_real[fftsize]; | |||
| fft = new fftw_complex[fftsize + 1]; | |||
| pthread_mutex_lock(mutex); | |||
| planfftw = fftw_plan_dft_r2c_1d(fftsize, | |||
| time, | |||
| fft, | |||
| @@ -38,12 +49,15 @@ FFTwrapper::FFTwrapper(int fftsize_) | |||
| fft, | |||
| time, | |||
| FFTW_ESTIMATE); | |||
| pthread_mutex_unlock(mutex); | |||
| } | |||
| FFTwrapper::~FFTwrapper() | |||
| { | |||
| pthread_mutex_lock(mutex); | |||
| fftw_destroy_plan(planfftw); | |||
| fftw_destroy_plan(planfftw_inv); | |||
| pthread_mutex_unlock(mutex); | |||
| delete [] time; | |||
| delete [] fft; | |||
| @@ -82,4 +96,7 @@ void FFTwrapper::freqs2smps(const fft_t *freqs, float *smps) | |||
| void FFT_cleanup() | |||
| { | |||
| fftw_cleanup(); | |||
| pthread_mutex_destroy(mutex); | |||
| delete mutex; | |||
| mutex = NULL; | |||
| } | |||
| @@ -24,8 +24,7 @@ | |||
| #define FFT_WRAPPER_H | |||
| #include <fftw3.h> | |||
| #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)*/ | |||
| class FFTwrapper | |||
| @@ -48,5 +47,24 @@ class FFTwrapper | |||
| 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(); | |||
| #endif | |||
| @@ -20,14 +20,16 @@ | |||
| */ | |||
| #include <math.h> | |||
| #include <stdio.h> | |||
| #include <cmath> | |||
| #include <cstdio> | |||
| #include <cassert> | |||
| #include "Filter.h" | |||
| #include "AnalogFilter.h" | |||
| #include "FormantFilter.h" | |||
| #include "SVFilter.h" | |||
| #include "../Params/FilterParams.h" | |||
| #include "../Misc/Allocator.h" | |||
| Filter::Filter(unsigned int srate, int bufsize) | |||
| : outgain(1.0f), | |||
| @@ -37,12 +39,11 @@ Filter::Filter(unsigned int srate, int bufsize) | |||
| 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 Fstages = pars->Pstages; | |||
| @@ -50,16 +51,16 @@ Filter *Filter::generate(FilterParams *pars, unsigned int srate, int bufsize) | |||
| Filter *filter; | |||
| switch(pars->Pcategory) { | |||
| case 1: | |||
| filter = new FormantFilter(pars, srate, bufsize); | |||
| filter = memory.alloc<FormantFilter>(pars, &memory, srate, bufsize); | |||
| break; | |||
| 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()); | |||
| if(filter->outgain > 1.0f) | |||
| filter->outgain = sqrt(filter->outgain); | |||
| break; | |||
| 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)) | |||
| filter->setgain(pars->getgain()); | |||
| else | |||
| @@ -29,7 +29,8 @@ class Filter | |||
| { | |||
| public: | |||
| 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); | |||
| virtual ~Filter() {} | |||
| @@ -23,16 +23,17 @@ | |||
| #include <cmath> | |||
| #include <cstdio> | |||
| #include "../Misc/Util.h" | |||
| #include "../Misc/Allocator.h" | |||
| #include "FormantFilter.h" | |||
| #include "AnalogFilter.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; | |||
| 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(); | |||
| for(int j = 0; j < FF_MAX_VOWELS; ++j) | |||
| @@ -78,7 +79,7 @@ FormantFilter::FormantFilter(FilterParams *pars, unsigned int srate, int bufsize | |||
| FormantFilter::~FormantFilter() | |||
| { | |||
| for(int i = 0; i < numformants; ++i) | |||
| delete (formant[i]); | |||
| memory.dealloc(formant[i]); | |||
| } | |||
| void FormantFilter::cleanup() | |||
| @@ -27,10 +27,11 @@ | |||
| #include "Filter.h" | |||
| class Allocator; | |||
| class FormantFilter:public Filter | |||
| { | |||
| public: | |||
| FormantFilter(class FilterParams *pars, unsigned int srate, int bufsize); | |||
| FormantFilter(class FilterParams *pars, Allocator *alloc, unsigned int srate, int bufsize); | |||
| ~FormantFilter(); | |||
| void filterout(float *smp); | |||
| void setfreq(float frequency); | |||
| @@ -61,6 +62,7 @@ class FormantFilter:public Filter | |||
| float oldinput, slowinput; | |||
| float Qfactor, formantslowness, oldQfactor; | |||
| float vowelclearness, sequencestretch; | |||
| Allocator &memory; | |||
| }; | |||
| #endif | |||
| @@ -24,9 +24,7 @@ | |||
| #include <cstdio> | |||
| #include <cstring> | |||
| #include <cassert> | |||
| #ifndef CARLA_OS_WIN | |||
| #include <err.h> | |||
| #endif | |||
| #include "../Misc/Util.h" | |||
| #include "SVFilter.h" | |||
| @@ -21,13 +21,12 @@ | |||
| #include <cmath> | |||
| #include <cstring> | |||
| #ifndef CARLA_OS_WIN | |||
| #include <err.h> | |||
| #endif | |||
| #include "../Misc/Allocator.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), | |||
| base_freq(1.0f), | |||
| uv(NULL), | |||
| @@ -39,18 +38,19 @@ Unison::Unison(int update_period_samples_, float max_delay_sec_, float srate_f) | |||
| delay_buffer(NULL), | |||
| unison_amplitude_samples(0.0f), | |||
| unison_bandwidth_cents(10.0f), | |||
| samplerate_f(srate_f) | |||
| samplerate_f(srate_f), | |||
| alloc(*alloc_) | |||
| { | |||
| if(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)); | |||
| setSize(1); | |||
| } | |||
| Unison::~Unison() { | |||
| delete [] delay_buffer; | |||
| delete [] uv; | |||
| alloc.devalloc(delay_buffer); | |||
| alloc.devalloc(uv); | |||
| } | |||
| void Unison::setSize(int new_size) | |||
| @@ -58,9 +58,8 @@ void Unison::setSize(int new_size) | |||
| if(new_size < 1) | |||
| new_size = 1; | |||
| 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; | |||
| updateParameters(); | |||
| } | |||
| @@ -26,11 +26,12 @@ | |||
| //how much the unison frequencies varies (always >= 1.0) | |||
| #define UNISON_FREQ_SPAN 2.0f | |||
| class Allocator; | |||
| class Unison | |||
| { | |||
| 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(); | |||
| void setSize(int new_size); | |||
| @@ -72,5 +73,6 @@ class Unison | |||
| // current setup | |||
| float samplerate_f; | |||
| Allocator &alloc; | |||
| }; | |||
| #endif | |||
| @@ -21,11 +21,14 @@ | |||
| */ | |||
| #include <cmath> | |||
| #include "../Misc/Allocator.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), | |||
| oldr(NULL) | |||
| { | |||
| @@ -37,10 +40,8 @@ Alienwah::Alienwah(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned i | |||
| 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) | |||
| { | |||
| 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; | |||
| oldl = new complex<float>[Pdelay]; | |||
| oldr = new complex<float>[Pdelay]; | |||
| oldl = memory.valloc<complex<float>>(Pdelay); | |||
| oldr = memory.valloc<complex<float>>(Pdelay); | |||
| cleanup(); | |||
| } | |||
| @@ -23,11 +23,9 @@ | |||
| #ifndef ALIENWAH_H | |||
| #define ALIENWAH_H | |||
| #include <complex> | |||
| #include "Effect.h" | |||
| #include "EffectLFO.h" | |||
| using namespace std; | |||
| #include <complex> | |||
| #define MAX_ALIENWAH_DELAY 100 | |||
| @@ -35,17 +33,7 @@ using namespace std; | |||
| class Alienwah:public Effect | |||
| { | |||
| 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(); | |||
| void out(const Stereo<float *> &smp); | |||
| @@ -73,8 +61,8 @@ class Alienwah:public Effect | |||
| //Internal Values | |||
| float fb, depth, phase; | |||
| complex<float> *oldl, *oldr; | |||
| complex<float> oldclfol, oldclfor; | |||
| std::complex<float> *oldl, *oldr; | |||
| std::complex<float> oldclfol, oldclfor; | |||
| int oldk; | |||
| }; | |||
| @@ -21,16 +21,17 @@ | |||
| */ | |||
| #include <cmath> | |||
| #include "../Misc/Allocator.h" | |||
| #include "Chorus.h" | |||
| #include <iostream> | |||
| 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)), | |||
| delaySample(new float[maxdelay], new float[maxdelay]) | |||
| delaySample(memory.valloc<float>(maxdelay), memory.valloc<float>(maxdelay)) | |||
| { | |||
| dlk = 0; | |||
| drk = 0; | |||
| @@ -44,8 +45,8 @@ Chorus::Chorus(bool insertion_, float *const efxoutl_, float *efxoutr_, unsigned | |||
| 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 | |||
| @@ -32,7 +32,7 @@ | |||
| class Chorus:public Effect | |||
| { | |||
| public: | |||
| Chorus(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||
| Chorus(EffectParams pars); | |||
| /**Destructor*/ | |||
| ~Chorus(); | |||
| void out(const Stereo<float *> &input); | |||
| @@ -23,10 +23,11 @@ | |||
| #include "Distorsion.h" | |||
| #include "../DSP/AnalogFilter.h" | |||
| #include "../Misc/WaveShapeSmps.h" | |||
| #include "../Misc/Allocator.h" | |||
| #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), | |||
| Pdrive(90), | |||
| Plevel(64), | |||
| @@ -37,20 +38,20 @@ Distorsion::Distorsion(bool insertion_, float *efxoutl_, float *efxoutr_, unsign | |||
| Pstereo(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); | |||
| cleanup(); | |||
| } | |||
| 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 | |||
| @@ -137,7 +138,7 @@ void Distorsion::setvolume(unsigned char _Pvolume) | |||
| void Distorsion::setlpf(unsigned char _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); | |||
| lpfr->setfreq(fr); | |||
| } | |||
| @@ -145,7 +146,7 @@ void Distorsion::setlpf(unsigned char _Plpf) | |||
| void Distorsion::sethpf(unsigned char _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); | |||
| hpfr->setfreq(fr); | |||
| } | |||
| @@ -29,7 +29,7 @@ | |||
| class Distorsion:public Effect | |||
| { | |||
| public: | |||
| Distorsion(bool insertion, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||
| Distorsion(EffectParams pars); | |||
| ~Distorsion(); | |||
| void out(const Stereo<float *> &smp); | |||
| void setpreset(unsigned char npreset); | |||
| @@ -23,10 +23,11 @@ | |||
| #include <cmath> | |||
| #include "DynamicFilter.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), | |||
| Pdepth(0), | |||
| Pampsns(90), | |||
| @@ -35,15 +36,16 @@ DynamicFilter::DynamicFilter(bool insertion_, float *efxoutl_, float *efxoutr_, | |||
| filterl(NULL), | |||
| filterr(NULL) | |||
| { | |||
| filterpars = memory.alloc<FilterParams>(0,0,0); | |||
| setpreset(Ppreset); | |||
| cleanup(); | |||
| } | |||
| 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) | |||
| { | |||
| 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) | |||
| @@ -30,7 +30,7 @@ | |||
| class DynamicFilter:public Effect | |||
| { | |||
| public: | |||
| DynamicFilter(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||
| DynamicFilter(EffectParams pars); | |||
| ~DynamicFilter(); | |||
| void out(const Stereo<float *> &smp); | |||
| @@ -23,9 +23,10 @@ | |||
| #include <cmath> | |||
| #include "EQ.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) { | |||
| 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].Pq = 64; | |||
| 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 | |||
| Pvolume = 50; | |||
| @@ -43,6 +44,13 @@ EQ::EQ(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, in | |||
| 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 | |||
| void EQ::cleanup(void) | |||
| @@ -196,3 +204,42 @@ float EQ::getfreqresponse(float freq) | |||
| } | |||
| 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 | |||
| { | |||
| 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 setpreset(unsigned char npreset); | |||
| void changepar(int npar, unsigned char value); | |||
| @@ -38,6 +38,8 @@ class EQ:public Effect | |||
| void cleanup(void); | |||
| 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: | |||
| //Parameters | |||
| unsigned char Pvolume; | |||
| @@ -48,7 +50,13 @@ class EQ:public Effect | |||
| //parameters | |||
| unsigned char Ptype, Pfreq, Pgain, Pq, Pstages; | |||
| //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]; | |||
| }; | |||
| @@ -23,12 +23,13 @@ | |||
| */ | |||
| #include <cmath> | |||
| #include "../Misc/Allocator.h" | |||
| #include "Echo.h" | |||
| #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), | |||
| Pdelay(60), | |||
| Plrdelay(100), | |||
| @@ -37,8 +38,8 @@ Echo::Echo(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate | |||
| delayTime(1), | |||
| lrdelay(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), | |||
| pos(0), | |||
| delta(1), | |||
| @@ -50,8 +51,8 @@ Echo::Echo(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate | |||
| Echo::~Echo() | |||
| { | |||
| delete[] delay.l; | |||
| delete[] delay.r; | |||
| memory.devalloc(delay.l); | |||
| memory.devalloc(delay.r); | |||
| } | |||
| //Cleanup the effect | |||
| @@ -79,6 +80,7 @@ void Echo::initdelays(void) | |||
| ndelta.l = max(1, (int) (dl * samplerate)); | |||
| ndelta.r = max(1, (int) (dr * samplerate)); | |||
| delta = ndelta; | |||
| } | |||
| //Effect output | |||
| @@ -30,7 +30,7 @@ | |||
| class Echo:public Effect | |||
| { | |||
| public: | |||
| Echo(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||
| Echo(EffectParams pars); | |||
| ~Echo(); | |||
| void out(const Stereo<float *> &input); | |||
| @@ -25,16 +25,20 @@ | |||
| #include "../Params/FilterParams.h" | |||
| #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(); | |||
| } | |||
| @@ -29,22 +29,38 @@ | |||
| #include "../Misc/Stereo.h" | |||
| 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, ..)*/ | |||
| class Effect | |||
| { | |||
| 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() {} | |||
| /** | |||
| * Choose a preset | |||
| @@ -102,6 +118,9 @@ class Effect | |||
| char Plrcross; // L/R mix | |||
| float lrcross; | |||
| //Allocator | |||
| Allocator &memory; | |||
| // current setup | |||
| unsigned int samplerate; | |||
| int buffersize; | |||
| @@ -20,6 +20,10 @@ | |||
| */ | |||
| #include <rtosc/ports.h> | |||
| #include <rtosc/port-sugar.h> | |||
| #include "EffectMgr.h" | |||
| #include "Effect.h" | |||
| #include "Reverb.h" | |||
| @@ -29,31 +33,103 @@ | |||
| #include "EQ.h" | |||
| #include "DynamicFilter.h" | |||
| #include "../Misc/XMLwrapper.h" | |||
| #include "../Misc/Util.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_), | |||
| efxoutl(new float[synth->buffersize]), | |||
| efxoutr(new float[synth->buffersize]), | |||
| efxoutl(new float[synth_.buffersize]), | |||
| efxoutr(new float[synth_.buffersize]), | |||
| filterpars(NULL), | |||
| nefx(0), | |||
| efx(NULL), | |||
| mutex(mutex_), | |||
| dryonly(false) | |||
| dryonly(false), | |||
| memory(alloc), | |||
| synth(synth_) | |||
| { | |||
| 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(); | |||
| } | |||
| EffectMgr::~EffectMgr() | |||
| { | |||
| delete efx; | |||
| memory.dealloc(efx); | |||
| delete [] efxoutl; | |||
| delete [] efxoutr; | |||
| } | |||
| @@ -65,39 +141,41 @@ void EffectMgr::defaults(void) | |||
| } | |||
| //Change the effect | |||
| void EffectMgr::changeeffect(int _nefx) | |||
| void EffectMgr::changeeffectrt(int _nefx) | |||
| { | |||
| cleanup(); | |||
| if(nefx == _nefx) | |||
| if(nefx == _nefx && efx != NULL) | |||
| return; | |||
| 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) { | |||
| case 1: | |||
| efx = new Reverb(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||
| efx = memory.alloc<Reverb>(pars); | |||
| break; | |||
| case 2: | |||
| efx = new Echo(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||
| efx = memory.alloc<Echo>(pars); | |||
| break; | |||
| case 3: | |||
| efx = new Chorus(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||
| efx = memory.alloc<Chorus>(pars); | |||
| break; | |||
| case 4: | |||
| efx = new Phaser(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||
| efx = memory.alloc<Phaser>(pars); | |||
| break; | |||
| case 5: | |||
| efx = new Alienwah(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||
| efx = memory.alloc<Alienwah>(pars); | |||
| break; | |||
| case 6: | |||
| efx = new Distorsion(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||
| efx = memory.alloc<Distorsion>(pars); | |||
| break; | |||
| case 7: | |||
| efx = new EQ(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||
| efx = memory.alloc<EQ>(pars); | |||
| break; | |||
| case 8: | |||
| efx = new DynamicFilter(insertion, efxoutl, efxoutr, synth->samplerate, synth->buffersize); | |||
| efx = memory.alloc<DynamicFilter>(pars); | |||
| break; | |||
| //put more effect here | |||
| default: | |||
| @@ -109,12 +187,35 @@ void EffectMgr::changeeffect(int _nefx) | |||
| filterpars = efx->filterpars; | |||
| } | |||
| void EffectMgr::changeeffect(int _nefx) | |||
| { | |||
| nefx = _nefx; | |||
| //preset = 0; | |||
| //memset(settings, 0, sizeof(settings)); | |||
| } | |||
| //Obtain the effect number | |||
| int EffectMgr::geteffect(void) | |||
| { | |||
| 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 | |||
| void EffectMgr::cleanup(void) | |||
| { | |||
| @@ -133,39 +234,47 @@ unsigned char EffectMgr::getpreset(void) | |||
| } | |||
| // 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 | |||
| 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) | |||
| return; | |||
| 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) | |||
| { | |||
| pthread_mutex_lock(mutex); | |||
| seteffectpar_nolock(npar, value); | |||
| pthread_mutex_unlock(mutex); | |||
| settings[npar] = value; | |||
| } | |||
| //Get a parameter of the current effect | |||
| 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) | |||
| return 0; | |||
| @@ -177,7 +286,7 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||
| { | |||
| if(!efx) { | |||
| if(!insertion) | |||
| for(int i = 0; i < synth->buffersize; ++i) { | |||
| for(int i = 0; i < synth.buffersize; ++i) { | |||
| smpsl[i] = 0.0f; | |||
| smpsr[i] = 0.0f; | |||
| efxoutl[i] = 0.0f; | |||
| @@ -185,7 +294,7 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||
| } | |||
| return; | |||
| } | |||
| for(int i = 0; i < synth->buffersize; ++i) { | |||
| for(int i = 0; i < synth.buffersize; ++i) { | |||
| smpsl[i] += denormalkillbuf[i]; | |||
| smpsr[i] += denormalkillbuf[i]; | |||
| efxoutl[i] = 0.0f; | |||
| @@ -196,8 +305,8 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||
| float volume = efx->volume; | |||
| 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; | |||
| } | |||
| @@ -216,20 +325,20 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||
| v2 *= v2; //for Reverb and Echo, the wet function is not liniar | |||
| 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; | |||
| smpsr[i] *= v1; | |||
| efxoutl[i] *= v2; | |||
| efxoutr[i] *= v2; | |||
| } | |||
| 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; | |||
| smpsr[i] = smpsr[i] * v1 + efxoutr[i] * v2; | |||
| } | |||
| } | |||
| else // System effect | |||
| for(int i = 0; i < synth->buffersize; ++i) { | |||
| for(int i = 0; i < synth.buffersize; ++i) { | |||
| efxoutl[i] *= 2.0f * volume; | |||
| efxoutr[i] *= 2.0f * volume; | |||
| smpsl[i] = efxoutl[i]; | |||
| @@ -241,7 +350,7 @@ void EffectMgr::out(float *smpsl, float *smpsr) | |||
| // Get the effect volume for the system effect | |||
| 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; | |||
| } | |||
| 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) | |||
| { | |||
| xml->addpar("type", geteffect()); | |||
| if(!efx || !geteffect()) | |||
| if(!geteffect()) | |||
| return; | |||
| xml->addpar("preset", efx->Ppreset); | |||
| xml->addpar("preset", preset); | |||
| xml->beginbranch("EFFECT_PARAMETERS"); | |||
| for(int n = 0; n < 128; ++n) { | |||
| @@ -286,18 +404,18 @@ void EffectMgr::getfromXML(XMLwrapper *xml) | |||
| { | |||
| changeeffect(xml->getpar127("type", geteffect())); | |||
| if(!efx || !geteffect()) | |||
| if(!geteffect()) | |||
| return; | |||
| efx->Ppreset = xml->getpar127("preset", efx->Ppreset); | |||
| preset = xml->getpar127("preset", preset); | |||
| if(xml->enterbranch("EFFECT_PARAMETERS")) { | |||
| 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) | |||
| continue; | |||
| int par = geteffectpar(n); | |||
| seteffectpar_nolock(n, xml->getpar127("par", par)); | |||
| seteffectpar(n, xml->getpar127("par", par)); | |||
| xml->exitbranch(); | |||
| } | |||
| if(filterpars) | |||
| @@ -31,11 +31,11 @@ | |||
| class Effect; | |||
| class FilterParams; | |||
| class XMLwrapper; | |||
| class Allocator; | |||
| #include "Distorsion.h" | |||
| #include "EQ.h" | |||
| #include "DynamicFilter.h" | |||
| #include "../Misc/XMLwrapper.h" | |||
| #include "../Params/FilterParams.h" | |||
| #include "../Params/Presets.h" | |||
| @@ -43,30 +43,35 @@ class XMLwrapper; | |||
| class EffectMgr:public Presets | |||
| { | |||
| public: | |||
| EffectMgr(const bool insertion_, pthread_mutex_t *mutex_); | |||
| EffectMgr(Allocator &alloc, const SYNTH_T &synth, const bool insertion_); | |||
| ~EffectMgr(); | |||
| void paste(EffectMgr &e); | |||
| void add2XML(XMLwrapper *xml); | |||
| void defaults(void); | |||
| void defaults(void) REALTIME; | |||
| void getfromXML(XMLwrapper *xml); | |||
| void out(float *smpsl, float *smpsr); | |||
| void out(float *smpsl, float *smpsr) REALTIME; | |||
| void setdryonly(bool value); | |||
| /**get the output(to speakers) volume of the systemeffect*/ | |||
| 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); | |||
| 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); | |||
| 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 geteffectparrt(int npar) REALTIME; | |||
| const bool insertion; | |||
| float *efxoutl, *efxoutr; | |||
| @@ -76,11 +81,19 @@ class EffectMgr:public Presets | |||
| FilterParams *filterpars; | |||
| private: | |||
| static const rtosc::Ports &ports; | |||
| int nefx; | |||
| Effect *efx; | |||
| pthread_mutex_t *mutex; | |||
| private: | |||
| //Parameters Prior to initialization | |||
| char effect_id; | |||
| char preset; | |||
| char settings[128]; | |||
| bool dryonly; | |||
| Allocator &memory; | |||
| const SYNTH_T &synth; | |||
| }; | |||
| #endif | |||
| @@ -31,6 +31,7 @@ | |||
| #include <cmath> | |||
| #include <algorithm> | |||
| #include "../Misc/Allocator.h" | |||
| #include "Phaser.h" | |||
| 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 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) | |||
| { | |||
| analog_setup(); | |||
| @@ -78,18 +79,12 @@ void Phaser::analog_setup() | |||
| 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) | |||
| { | |||
| 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); | |||
| 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(); | |||
| } | |||
| @@ -35,7 +35,7 @@ | |||
| class Phaser:public Effect | |||
| { | |||
| public: | |||
| Phaser(const int &insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||
| Phaser(EffectParams pars); | |||
| ~Phaser(); | |||
| void out(const Stereo<float *> &input); | |||
| void setpreset(unsigned char npreset); | |||
| @@ -22,12 +22,13 @@ | |||
| #include "Reverb.h" | |||
| #include "../Misc/Util.h" | |||
| #include "../Misc/Allocator.h" | |||
| #include "../DSP/AnalogFilter.h" | |||
| #include "../DSP/Unison.h" | |||
| #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 | |||
| Pvolume(48), | |||
| Ptime(64), | |||
| @@ -39,6 +40,7 @@ Reverb::Reverb(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int s | |||
| Ptype(1), | |||
| Proomsize(64), | |||
| Pbandwidth(30), | |||
| idelaylen(0), | |||
| roomsize(1.0f), | |||
| rs(1.0f), | |||
| bandwidth(NULL), | |||
| @@ -66,35 +68,33 @@ Reverb::Reverb(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int s | |||
| 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) | |||
| delete [] ap[i]; | |||
| memory.devalloc(ap[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 | |||
| 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; | |||
| for(j = 0; j < comblen[i]; ++j) | |||
| for(int j = 0; j < comblen[i]; ++j) | |||
| 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; | |||
| if(idelay) | |||
| for(i = 0; i < idelaylen; ++i) | |||
| for(int i = 0; i < idelaylen; ++i) | |||
| idelay[i] = 0.0f; | |||
| if(hpf) | |||
| hpf->cleanup(); | |||
| @@ -231,15 +231,16 @@ void Reverb::setidelay(unsigned char _Pidelay) | |||
| { | |||
| Pidelay = _Pidelay; | |||
| 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) { | |||
| idelayk = 0; | |||
| idelay = new float[idelaylen]; | |||
| idelay = memory.valloc<float>(idelaylen); | |||
| memset(idelay, 0, idelaylen * sizeof(float)); | |||
| } | |||
| } | |||
| @@ -254,14 +255,11 @@ void Reverb::sethpf(unsigned char _Phpf) | |||
| { | |||
| Phpf = _Phpf; | |||
| 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) | |||
| hpf = new AnalogFilter(3, fr, 1, 0, samplerate, buffersize); | |||
| hpf = memory.alloc<AnalogFilter>(3, fr, 1, 0, samplerate, buffersize); | |||
| else | |||
| hpf->setfreq(fr); | |||
| } | |||
| @@ -271,14 +269,11 @@ void Reverb::setlpf(unsigned char _Plpf) | |||
| { | |||
| Plpf = _Plpf; | |||
| 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) | |||
| lpf = new AnalogFilter(2, fr, 1, 0, samplerate, buffersize); | |||
| lpf = memory.alloc<AnalogFilter>(2, fr, 1, 0, samplerate, buffersize); | |||
| else | |||
| lpf->setfreq(fr); | |||
| } | |||
| @@ -323,12 +318,13 @@ void Reverb::settype(unsigned char _Ptype) | |||
| tmp *= samplerate_adjust; //adjust the combs according to the samplerate | |||
| if(tmp < 10.0f) | |||
| tmp = 10.0f; | |||
| comblen[i] = (int) tmp; | |||
| combk[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) { | |||
| @@ -342,20 +338,20 @@ void Reverb::settype(unsigned char _Ptype) | |||
| tmp *= samplerate_adjust; //adjust the combs according to the samplerate | |||
| if(tmp < 10) | |||
| tmp = 10; | |||
| aplen[i] = (int) tmp; | |||
| 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 | |||
| //TODO the size of the unison buffer may be too small, though this has | |||
| //not been verified yet. | |||
| //As this cannot be resized in a RT context, a good upper bound should | |||
| //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->setBaseFrequency(1.0f); | |||
| } | |||
| @@ -32,7 +32,7 @@ | |||
| class Reverb:public Effect | |||
| { | |||
| public: | |||
| Reverb(bool insertion_, float *efxoutl_, float *efxoutr_, unsigned int srate, int bufsize); | |||
| Reverb(EffectParams pars); | |||
| ~Reverb(); | |||
| void out(const Stereo<float *> &smp); | |||
| 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 <string.h> | |||
| #include <stdio.h> | |||
| #include <stdlib.h> | |||
| #include <cstring> | |||
| #include <cstdio> | |||
| #include <cstdlib> | |||
| #include <dirent.h> | |||
| #include <sys/stat.h> | |||
| #include <algorithm> | |||
| #include <iostream> | |||
| #include <sys/types.h> | |||
| #include <fcntl.h> | |||
| @@ -48,11 +47,19 @@ | |||
| using namespace std; | |||
| Bank::Bank() | |||
| :defaultinsname(" ") | |||
| :bankpos(0), defaultinsname(" ") | |||
| { | |||
| clearbank(); | |||
| bankfiletitle = dirname; | |||
| rescanforbanks(); | |||
| loadbank(config.cfg.currentBankDir); | |||
| for(unsigned i=0; i<banks.size(); ++i) { | |||
| if(banks[i].dir == config.cfg.currentBankDir) { | |||
| bankpos = i; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| Bank::~Bank() | |||
| @@ -84,10 +91,10 @@ string Bank::getnamenumbered(unsigned int ninstrument) | |||
| /* | |||
| * 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)) | |||
| return; | |||
| return 0; | |||
| string newfilename; | |||
| char tmpfilename[100 + 1]; | |||
| @@ -103,12 +110,15 @@ void Bank::setname(unsigned int ninstrument, const string &newname, int newslot) | |||
| if(tmpfilename[i] == ' ') | |||
| 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].name = newname; | |||
| return err; | |||
| } | |||
| /* | |||
| @@ -121,30 +131,37 @@ bool Bank::emptyslot(unsigned int ninstrument) | |||
| if(ins[ninstrument].filename.empty()) | |||
| return true; | |||
| if(ins[ninstrument].used) | |||
| return false; | |||
| else | |||
| return true; | |||
| return false; | |||
| } | |||
| /* | |||
| * Removes the instrument from the bank | |||
| */ | |||
| void Bank::clearslot(unsigned int ninstrument) | |||
| int Bank::clearslot(unsigned int 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 | |||
| */ | |||
| 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; | |||
| char tmpfilename[maxfilename + 20]; | |||
| @@ -163,23 +180,35 @@ void Bank::savetoslot(unsigned int ninstrument, Part *part) | |||
| 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); | |||
| return 0; | |||
| } | |||
| /* | |||
| * 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)) | |||
| return; | |||
| return 0; | |||
| part->AllNotesOff(); | |||
| part->defaultsinstrument(); | |||
| part->loadXMLinstrument(ins[ninstrument].filename.c_str()); | |||
| return 0; | |||
| } | |||
| /* | |||
| @@ -251,7 +280,6 @@ int Bank::loadbank(string bankdirname) | |||
| */ | |||
| int Bank::newbank(string newbankdirname) | |||
| { | |||
| #ifndef CARLA_OS_WIN | |||
| string bankdir; | |||
| bankdir = config.cfg.bankRootDirList[0]; | |||
| @@ -269,9 +297,6 @@ int Bank::newbank(string newbankdirname) | |||
| fclose(tmpfile); | |||
| return loadbank(bankdir); | |||
| #else | |||
| return -1; | |||
| #endif | |||
| } | |||
| /* | |||
| @@ -279,23 +304,27 @@ int Bank::newbank(string newbankdirname) | |||
| */ | |||
| int Bank::locked() | |||
| { | |||
| //XXX Fixme | |||
| return dirname.empty(); | |||
| } | |||
| /* | |||
| * 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())) | |||
| return; | |||
| return 0; | |||
| if(emptyslot(n1) && (emptyslot(n2))) | |||
| return; | |||
| return 0; | |||
| if(emptyslot(n1)) //change n1 to n2 in order to make | |||
| swap(n1, n2); | |||
| 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[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 | |||
| 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]); | |||
| } | |||
| return err; | |||
| } | |||
| @@ -413,7 +445,7 @@ void Bank::clearbank() | |||
| int Bank::addtobank(int pos, string filename, string name) | |||
| { | |||
| 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 | |||
| } | |||
| else | |||
| @@ -423,7 +455,7 @@ int Bank::addtobank(int pos, string filename, string name) | |||
| if(pos < 0) //find a free position | |||
| for(int i = BANK_SIZE - 1; i >= 0; i--) | |||
| if(!ins[i].used) { | |||
| if(ins[i].filename.empty()) { | |||
| pos = i; | |||
| break; | |||
| } | |||
| @@ -433,32 +465,11 @@ int Bank::addtobank(int pos, string filename, string name) | |||
| deletefrombank(pos); | |||
| ins[pos].used = true; | |||
| 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; | |||
| } | |||
| 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) | |||
| { | |||
| if((pos < 0) || (pos >= BANK_SIZE)) | |||
| @@ -467,7 +478,5 @@ void Bank::deletefrombank(int pos) | |||
| } | |||
| Bank::ins_t::ins_t() | |||
| :used(false), name(""), filename("") | |||
| { | |||
| info.PADsynth_used = false; | |||
| } | |||
| :name(""), filename("") | |||
| {} | |||
| @@ -25,6 +25,7 @@ | |||
| #include <string> | |||
| #include <vector> | |||
| #include "../globals.h" | |||
| //entries in a bank | |||
| #define BANK_SIZE 160 | |||
| @@ -38,26 +39,26 @@ class Bank | |||
| ~Bank(); | |||
| std::string getname(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, | |||
| 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*/ | |||
| bool emptyslot(unsigned int ninstrument); | |||
| /**Empties out the selected slot*/ | |||
| void clearslot(unsigned int ninstrument); | |||
| int clearslot(unsigned int ninstrument); | |||
| /**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*/ | |||
| void loadfromslot(unsigned int ninstrument, class Part * part); | |||
| int loadfromslot(unsigned int ninstrument, class Part * part); | |||
| /**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) | |||
| int locked(); | |||
| @@ -71,6 +72,14 @@ class Bank | |||
| }; | |||
| 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: | |||
| @@ -84,17 +93,6 @@ class Bank | |||
| void clearbank(); | |||
| 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; | |||
| void scanrootdir(std::string rootdir); //scans a root dir for banks | |||
| @@ -3,7 +3,6 @@ include_directories(${MXML_INCLUDE_DIR}) | |||
| set(zynaddsubfx_misc_SRCS | |||
| Misc/Bank.cpp | |||
| Misc/Config.cpp | |||
| Misc/Dump.cpp | |||
| Misc/Master.cpp | |||
| Misc/Microtonal.cpp | |||
| Misc/Part.cpp | |||
| @@ -12,6 +11,9 @@ set(zynaddsubfx_misc_SRCS | |||
| Misc/Recorder.cpp | |||
| Misc/WavFile.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 | |||
| */ | |||
| #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 "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() | |||
| {} | |||
| void Config::init() | |||
| { | |||
| maxstringsize = MAX_STRING_SIZE; //for ui | |||
| @@ -46,14 +137,10 @@ void Config::init() | |||
| cfg.LinuxOSSSeqInDev = new char[MAX_STRING_SIZE]; | |||
| snprintf(cfg.LinuxOSSSeqInDev, MAX_STRING_SIZE, "/dev/sequencer"); | |||
| cfg.DumpFile = "zynaddsubfx_dump.txt"; | |||
| cfg.WindowsWaveOutId = 0; | |||
| cfg.WindowsMidiInId = 0; | |||
| cfg.BankUIAutoClose = 0; | |||
| cfg.DumpNotesToFile = 0; | |||
| cfg.DumpAppend = 1; | |||
| cfg.GzipCompression = 3; | |||
| @@ -168,16 +255,6 @@ void Config::readConfig(const char *filename) | |||
| 0, | |||
| 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, | |||
| 0, | |||
| @@ -259,10 +336,6 @@ void Config::saveConfig(const char *filename) | |||
| xmlcfg->addpar("swap_stereo", cfg.SwapStereo); | |||
| 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("check_pad_synth", cfg.CheckPADsynth); | |||
| @@ -40,10 +40,8 @@ class Config | |||
| int SampleRate, SoundBufferSize, OscilSize, SwapStereo; | |||
| int WindowsWaveOutId, WindowsMidiInId; | |||
| int BankUIAutoClose; | |||
| int DumpNotesToFile, DumpAppend; | |||
| int GzipCompression; | |||
| int Interpolation; | |||
| std::string DumpFile; | |||
| std::string bankRootDirList[MAX_BANK_ROOT_DIRS], currentBankDir; | |||
| std::string presetsDirList[MAX_BANK_ROOT_DIRS]; | |||
| int CheckPADsynth; | |||
| @@ -66,6 +64,7 @@ class Config | |||
| void init(); | |||
| void save(); | |||
| static rtosc::Ports &ports; | |||
| private: | |||
| void readConfig(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 "../Effects/EffectMgr.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 <sys/stat.h> | |||
| #include <sys/types.h> | |||
| #include <iostream> | |||
| #include <algorithm> | |||
| #include <cmath> | |||
| #include <atomic> | |||
| #include <unistd.h> | |||
| 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) | |||
| :outpeakl(0.0f), outpeakr(0.0f), maxoutpeakl(0.0f), maxoutpeakr(0.0f), | |||
| 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; | |||
| off = 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; | |||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | |||
| @@ -66,18 +306,43 @@ Master::Master() | |||
| } | |||
| 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 | |||
| 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 | |||
| for(int nefx = 0; nefx < NUM_SYS_EFX; ++nefx) | |||
| sysefx[nefx] = new EffectMgr(0, &mutex); | |||
| sysefx[nefx] = new EffectMgr(*memory, synth, 0); | |||
| 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() | |||
| @@ -112,36 +377,6 @@ void Master::defaults() | |||
| 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) | |||
| */ | |||
| @@ -191,38 +426,36 @@ void Master::polyphonicAftertouch(char chan, char note, char velocity) | |||
| */ | |||
| void Master::setController(char chan, int type, int par) | |||
| { | |||
| if(frozenState) | |||
| return; | |||
| midi.process(chan,type,par); | |||
| if((type == C_dataentryhi) || (type == C_dataentrylo) | |||
| || (type == C_nrpnhi) || (type == C_nrpnlo)) { //Process RPN and NRPN by the Master (ignore the chan) | |||
| ctl.setparameternumber(type, par); | |||
| int parhi = -1, parlo = -1, valhi = -1, vallo = -1; | |||
| 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) { | |||
| case 0x04: //System Effects | |||
| if(parlo < NUM_SYS_EFX) | |||
| sysefx[parlo]->seteffectpar_nolock(valhi, vallo); | |||
| ; | |||
| sysefx[parlo]->seteffectparrt(valhi, vallo); | |||
| break; | |||
| case 0x08: //Insertion Effects | |||
| if(parlo < NUM_INS_EFX) | |||
| insefx[parlo]->seteffectpar_nolock(valhi, vallo); | |||
| ; | |||
| insefx[parlo]->seteffectparrt(valhi, vallo); | |||
| break; | |||
| } | |||
| ; | |||
| } | |||
| else | |||
| 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 | |||
| 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)) | |||
| part[npart]->SetController(type, par); | |||
| ; | |||
| if(type == C_allsoundsoff) { //cleanup insertion/system FX | |||
| 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) | |||
| { | |||
| //Peak computation (for vumeters) | |||
| vu.outpeakl = 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) | |||
| vu.outpeakl = fabs(outl[i]); | |||
| if(fabs(outr[i]) > vu.outpeakr) | |||
| @@ -272,12 +487,12 @@ void Master::vuUpdate(const float *outl, const float *outr) | |||
| //RMS Peak computation (for vumeters) | |||
| vu.rmspeakl = 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.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) | |||
| 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) { | |||
| float *outl = part[npart]->partoutl, | |||
| *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]); | |||
| if(tmp > vuoutpeakpart[npart]) | |||
| vuoutpeakpart[npart] = tmp; | |||
| @@ -312,7 +527,6 @@ void Master::partonoff(int npart, int what) | |||
| for(int nefx = 0; nefx < NUM_INS_EFX; ++nefx) { | |||
| if(Pinsparts[nefx] == npart) | |||
| insefx[nefx]->cleanup(); | |||
| ; | |||
| } | |||
| } | |||
| 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) | |||
| */ | |||
| 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 | |||
| if(swaplr) | |||
| swap(outl, outr); | |||
| //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 | |||
| 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(); | |||
| pthread_mutex_unlock(&part[npart]->load_mutex); | |||
| } | |||
| } | |||
| //Insertion effects | |||
| 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) | |||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) { | |||
| if(part[npart]->Penabled == 0) | |||
| if(!part[npart]->Penabled) | |||
| continue; | |||
| Stereo<float> newvol(part[npart]->volume), | |||
| @@ -366,26 +689,29 @@ void Master::AudioOut(float *outl, float *outr) | |||
| newvol.l *= pan * 2.0f; | |||
| else | |||
| 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 | |||
| if(ABOVE_AMPLITUDE_THRESHOLD(oldvol.l, newvol.l) | |||
| || 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, | |||
| i, synth->buffersize), | |||
| i, synth.buffersize), | |||
| INTERPOLATE_AMPLITUDE(oldvol.r, newvol.r, | |||
| i, synth->buffersize)); | |||
| i, synth.buffersize)); | |||
| part[npart]->partoutl[i] *= vol.l; | |||
| part[npart]->partoutr[i] *= vol.r; | |||
| } | |||
| part[npart]->oldvolumel = newvol.l; | |||
| 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]->partoutr[i] *= newvol.r; | |||
| } | |||
| } | |||
| } | |||
| @@ -394,11 +720,11 @@ void Master::AudioOut(float *outl, float *outr) | |||
| if(sysefx[nefx]->geteffect() == 0) | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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; | |||
| 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) | |||
| if(Psysefxsend[nefxfrom][nefx] != 0) { | |||
| 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; | |||
| 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 | |||
| 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; | |||
| outr[i] += tmpmixr[i] * outvol; | |||
| } | |||
| @@ -441,7 +767,7 @@ void Master::AudioOut(float *outl, float *outr) | |||
| //Mix all parts | |||
| for(int npart = 0; npart < NUM_MIDI_PARTS; ++npart) | |||
| 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]; | |||
| outr[i] += part[npart]->partoutr[i]; | |||
| } | |||
| @@ -453,20 +779,17 @@ void Master::AudioOut(float *outl, float *outr) | |||
| //Master Volume | |||
| for(int i = 0; i < synth->buffersize; ++i) { | |||
| for(int i = 0; i < synth.buffersize; ++i) { | |||
| outl[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) | |||
| 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; | |||
| outr[i] *= tmp; | |||
| } | |||
| @@ -475,8 +798,6 @@ void Master::AudioOut(float *outl, float *outr) | |||
| //update the LFO's time | |||
| LFOParams::time++; | |||
| dump.inctick(); | |||
| } | |||
| //TODO review the respective code from yoshimi for this | |||
| @@ -489,8 +810,8 @@ void Master::GetAudioOutSamples(size_t nsamples, | |||
| off_t out_off = 0; | |||
| //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; | |||
| } | |||
| @@ -505,7 +826,7 @@ void Master::GetAudioOutSamples(size_t nsamples, | |||
| AudioOut(bufl, bufr); | |||
| off = 0; | |||
| out_off += smps; | |||
| smps = synth->buffersize; | |||
| smps = synth.buffersize; | |||
| } | |||
| else { //use some samples | |||
| memcpy(outl + out_off, bufl + off, sizeof(float) * nsamples); | |||
| @@ -530,9 +851,7 @@ Master::~Master() | |||
| delete sysefx[nefx]; | |||
| delete fft; | |||
| pthread_mutex_destroy(&mutex); | |||
| pthread_mutex_destroy(&vumutex); | |||
| delete memory; | |||
| } | |||
| @@ -588,28 +907,28 @@ void Master::ShutUp() | |||
| */ | |||
| void Master::vuresetpeaks() | |||
| { | |||
| pthread_mutex_lock(&vumutex); | |||
| vu.outpeakl = 1e-9; | |||
| vu.outpeakr = 1e-9; | |||
| vu.maxoutpeakl = 1e-9; | |||
| vu.maxoutpeakr = 1e-9; | |||
| 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) | |||
| @@ -673,9 +992,7 @@ int Master::getalldata(char **data) | |||
| xml->beginbranch("MASTER"); | |||
| pthread_mutex_lock(&mutex); | |||
| add2XML(xml); | |||
| pthread_mutex_unlock(&mutex); | |||
| xml->endbranch(); | |||
| @@ -695,9 +1012,7 @@ void Master::putalldata(char *data, int /*size*/) | |||
| if(xml->enterbranch("MASTER") == 0) | |||
| return; | |||
| pthread_mutex_lock(&mutex); | |||
| getfromXML(xml); | |||
| pthread_mutex_unlock(&mutex); | |||
| xml->exitbranch(); | |||
| @@ -733,6 +1048,7 @@ int Master::loadXML(const char *filename) | |||
| xml->exitbranch(); | |||
| delete (xml); | |||
| initialize_rt(); | |||
| return 0; | |||
| } | |||
| @@ -23,23 +23,17 @@ | |||
| #ifndef MASTER_H | |||
| #define MASTER_H | |||
| #include <pthread.h> | |||
| #include "../globals.h" | |||
| #include "Microtonal.h" | |||
| #include <rtosc/miditable.h> | |||
| #include <rtosc/ports.h> | |||
| #include "Bank.h" | |||
| #include "Recorder.h" | |||
| #include "Dump.h" | |||
| #include "XMLwrapper.h" | |||
| #include "../Params/Controller.h" | |||
| typedef enum { | |||
| MUTEX_TRYLOCK, MUTEX_LOCK, MUTEX_UNLOCK | |||
| } lockset; | |||
| extern Dump dump; | |||
| class Allocator; | |||
| struct vuData { | |||
| vuData(void); | |||
| @@ -55,12 +49,11 @@ class Master | |||
| { | |||
| public: | |||
| /** Constructor TODO make private*/ | |||
| Master(); | |||
| Master(const SYNTH_T &synth); | |||
| /** Destructor*/ | |||
| ~Master(); | |||
| static Master &getInstance(); | |||
| static void deleteInstance(); | |||
| void applyOscEvent(const char *event); | |||
| /**Saves all settings to a XML file | |||
| * @return 0 for ok or <0 if there is an error*/ | |||
| @@ -75,28 +68,27 @@ class Master | |||
| /**loads all settings from a XML file | |||
| * @return 0 for ok or -1 if there is an error*/ | |||
| 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); | |||
| /**get all data to a newly allocated array (used for VST) | |||
| * @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)*/ | |||
| 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 | |||
| void noteOn(char chan, char note, char velocity); | |||
| void noteOff(char chan, char note); | |||
| void polyphonicAftertouch(char chan, char note, char velocity); | |||
| void setController(char chan, int type, int par); | |||
| void setProgram(char chan, unsigned int pgm); | |||
| //void NRPN... | |||
| @@ -106,12 +98,12 @@ class Master | |||
| void vuUpdate(const float *outl, const float *outr); | |||
| /**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*/ | |||
| void GetAudioOutSamples(size_t nsamples, | |||
| unsigned samplerate, | |||
| float *outl, | |||
| float *outr); | |||
| float *outr) REALTIME; | |||
| void partonoff(int npart, int what); | |||
| @@ -146,11 +138,8 @@ class Master | |||
| //peaks for VU-meter | |||
| void vuresetpeaks(); | |||
| //get VU-meter data | |||
| vuData getVuData(); | |||
| //peaks for part VU-meters | |||
| /**\todo synchronize this with a mutex*/ | |||
| float vuoutpeakpart[NUM_MIDI_PARTS]; | |||
| 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 | |||
| Microtonal microtonal; | |||
| Bank bank; | |||
| //Strictly Non-RT instrument bank object | |||
| Bank bank; | |||
| 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; | |||
| 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 sysefxsend[NUM_SYS_EFX][NUM_SYS_EFX]; | |||
| 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" | |||
| #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() | |||
| { | |||
| Pname = new unsigned char[MICROTONAL_MAX_NAME_LEN]; | |||
| Pcomment = new unsigned char[MICROTONAL_MAX_NAME_LEN]; | |||
| defaults(); | |||
| } | |||
| @@ -76,10 +123,7 @@ void Microtonal::defaults() | |||
| } | |||
| Microtonal::~Microtonal() | |||
| { | |||
| delete [] Pname; | |||
| delete [] Pcomment; | |||
| } | |||
| {} | |||
| /* | |||
| * Get the size of the octave | |||
| @@ -106,13 +150,13 @@ float Microtonal::getnotefreq(int note, int keyshift) const | |||
| note = (int) Pinvertupdowncenter * 2 - note; | |||
| //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, | |||
| (note - PAnote | |||
| + keyshift) / 12.0f) * PAfreq * globalfinedetunerap; //12tET | |||
| + keyshift) / 12.0f) * PAfreq * globalfinedetunerap; | |||
| int scaleshift = | |||
| ((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(Pmappingenabled != 0) { | |||
| if(Pmappingenabled) { | |||
| if((note < Pfirstkey) || (note > Plastkey)) | |||
| return -1.0f; | |||
| //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 = | |||
| (deltanote == | |||
| 0) ? (1.0f) : (octave[(deltanote - 1) % octavesize].tuning); | |||
| if(deltanote != 0) | |||
| if(deltanote) | |||
| rap_anote_middlenote *= | |||
| powf(octave[octavesize - 1].tuning, | |||
| (deltanote - 1) / octavesize); | |||
| if(minus != 0) | |||
| if(minus) | |||
| rap_anote_middlenote = 1.0f / rap_anote_middlenote; | |||
| //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 *= PAfreq / rap_anote_middlenote; | |||
| freq *= globalfinedetunerap; | |||
| if(scaleshift != 0) | |||
| if(scaleshift) | |||
| freq /= octave[scaleshift - 1].tuning; | |||
| return freq * rap_keyshift; | |||
| } | |||
| @@ -189,11 +233,10 @@ float Microtonal::getnotefreq(int note, int keyshift) const | |||
| octave[(ntkey + octavesize - 1) % octavesize].tuning * powf(oct, | |||
| ntoct) | |||
| * PAfreq; | |||
| if(ntkey == 0) | |||
| if(!ntkey) | |||
| freq /= oct; | |||
| if(scaleshift != 0) | |||
| if(scaleshift) | |||
| 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; | |||
| return freq * rap_keyshift; | |||
| } | |||
| @@ -460,77 +503,51 @@ int Microtonal::loadkbm(const char *filename) | |||
| { | |||
| FILE *file = fopen(filename, "r"); | |||
| int x; | |||
| float tmpPAfreq = 440.0f; | |||
| char tmp[500]; | |||
| fseek(file, 0, SEEK_SET); | |||
| //loads the mapsize | |||
| if(loadline(file, &tmp[0]) != 0) | |||
| if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0) | |||
| 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 | |||
| if(loadline(file, &tmp[0]) != 0) | |||
| if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0) | |||
| 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 | |||
| 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; | |||
| 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 | |||
| 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; | |||
| if(x < 1) | |||
| x = 0; | |||
| if(x > 127) | |||
| x = 127; //just in case... | |||
| Pmiddlenote = x; | |||
| Pmiddlenote = limit(x, 0, 127); | |||
| //loads the reference note | |||
| if(loadline(file, &tmp[0]) != 0) | |||
| if(loadline(file, tmp) != 0 || sscanf(tmp, "%d", &x) == 0) | |||
| 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. | |||
| 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; | |||
| 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) | |||
| return 2; | |||
| //load the mappings | |||
| if(Pmapsize != 0) { | |||
| for(int nline = 0; nline < Pmapsize; ++nline) { | |||
| if(loadline(file, &tmp[0]) != 0) | |||
| if(loadline(file, tmp) != 0) | |||
| return 2; | |||
| if(sscanf(&tmp[0], "%d", &x) == 0) | |||
| if(sscanf(tmp, "%d", &x) == 0) | |||
| x = -1; | |||
| Pmapping[nline] = x; | |||
| } | |||
| @@ -23,13 +23,12 @@ | |||
| #ifndef MICROTONAL_H | |||
| #define MICROTONAL_H | |||
| #include <cstdio> | |||
| #include "../globals.h" | |||
| #include "XMLwrapper.h" | |||
| #define MAX_OCTAVE_SIZE 128 | |||
| #define MICROTONAL_MAX_NAME_LEN 120 | |||
| #include <stdio.h> | |||
| class XMLwrapper; | |||
| /**Tuning settings and microtonal capabilities*/ | |||
| @@ -102,9 +101,9 @@ class Microtonal | |||
| void texttomapping(const char *text); | |||
| /**Name of Microtonal tuning*/ | |||
| unsigned char *Pname; | |||
| char Pname[MICROTONAL_MAX_NAME_LEN]; | |||
| /**Comment about the tuning*/ | |||
| unsigned char *Pcomment; | |||
| char Pcomment[MICROTONAL_MAX_NAME_LEN]; | |||
| void add2XML(XMLwrapper *xml) const; | |||
| void getfromXML(XMLwrapper *xml); | |||
| @@ -115,9 +114,12 @@ class Microtonal | |||
| bool operator==(const Microtonal µ) const; | |||
| bool operator!=(const Microtonal µ) const; | |||
| static const rtosc::Ports ports; | |||
| private: | |||
| 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; | |||
| struct { | |||
| 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 "../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*/ | |||
| class Part | |||
| @@ -46,29 +36,29 @@ class Part | |||
| public: | |||
| /**Constructor | |||
| * @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*/ | |||
| ~Part(); | |||
| // Copy misc parameters not stored in .xiz format | |||
| void cloneTraits(Part &part) const REALTIME; | |||
| // Midi commands implemented | |||
| void NoteOn(unsigned char note, | |||
| unsigned char velocity, | |||
| int masterkeyshift); | |||
| void NoteOff(unsigned char note); | |||
| int masterkeyshift) REALTIME; | |||
| void NoteOff(unsigned char note) REALTIME; | |||
| void PolyphonicAftertouch(unsigned char note, | |||
| 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 */ | |||
| 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 | |||
| @@ -82,7 +72,11 @@ class Part | |||
| void defaults(); | |||
| 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 getfromXMLinstrument(XMLwrapper *xml); | |||
| @@ -90,22 +84,25 @@ class Part | |||
| void cleanup(bool final = false); | |||
| //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; | |||
| ADnoteParameters *adpars; | |||
| SUBnoteParameters *subpars; | |||
| PADnoteParameters *padpars; | |||
| const static rtosc::Ports &ports; | |||
| } kit[NUM_KIT_ITEMS]; | |||
| //Part parameters | |||
| 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 Pminkey; /**<the minimum 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); | |||
| unsigned char Pvelsns; //velocity sensing (amplitude velocity scale) | |||
| 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 | |||
| 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; | |||
| @@ -139,7 +136,7 @@ class Part | |||
| *partfxinputr[NUM_PART_EFX + 1]; //partfxinput l/r [NUM_PART_EFX] is for "no effect" buffer | |||
| 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 | |||
| @@ -151,16 +148,14 @@ class Part | |||
| 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 | |||
| pthread_mutex_t *mutex; | |||
| pthread_mutex_t load_mutex; | |||
| int lastnote; | |||
| const static rtosc::Ports &ports; | |||
| private: | |||
| void RunNote(unsigned k); | |||
| void KillNotePos(int pos); | |||
| void RelaseNotePos(int pos); | |||
| void ReleaseNotePos(int pos); | |||
| void MonoMemRenote(); // MonoMem stuff. | |||
| 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 itemsplaying; | |||
| struct { | |||
| SynthNote *adnote, | |||
| *subnote, | |||
| *padnote; | |||
| SynthNote *adnote, *subnote, *padnote; | |||
| int sendtoparteffect; | |||
| } kititem[NUM_KIT_ITEMS]; | |||
| int time; | |||
| @@ -182,7 +175,13 @@ class Part | |||
| bool lastlegatomodevalid; // To keep track of previous legatomodevalid. | |||
| // 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 { | |||
| unsigned char velocity; | |||
| 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). | |||
| 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 | |||
| Microtonal *microtonal; | |||
| FFTwrapper *fft; | |||
| Allocator &memory; | |||
| const SYNTH_T &synth; | |||
| }; | |||
| #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 "Recorder.h" | |||
| #include "WavFile.h" | |||
| #include "../globals.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() | |||
| @@ -45,7 +46,7 @@ int Recorder::preparefile(std::string filename_, int overwrite) | |||
| return 1; | |||
| } | |||
| Nio::waveNew(new WavFile(filename_, synth->samplerate, 2)); | |||
| Nio::waveNew(new WavFile(filename_, synth.samplerate, 2)); | |||
| status = 1; //ready | |||
| @@ -23,14 +23,14 @@ | |||
| #ifndef RECORDER_H | |||
| #define RECORDER_H | |||
| #include <string> | |||
| #include "../globals.h" | |||
| struct SYNTH_T; | |||
| /**Records sound to a file*/ | |||
| class Recorder | |||
| { | |||
| public: | |||
| Recorder(); | |||
| Recorder(const SYNTH_T &synth); | |||
| ~Recorder(); | |||
| /**Prepare the given file. | |||
| * @returns 1 if the file exists */ | |||
| @@ -49,6 +49,7 @@ class Recorder | |||
| private: | |||
| int notetrigger; | |||
| const SYNTH_T &synth; | |||
| }; | |||
| #endif | |||
| @@ -23,11 +23,10 @@ | |||
| #include "Util.h" | |||
| #include <vector> | |||
| #include <cassert> | |||
| #include <math.h> | |||
| #include <stdio.h> | |||
| #ifndef CARLA_OS_WIN | |||
| #include <cmath> | |||
| #include <cstdio> | |||
| #include <fstream> | |||
| #include <err.h> | |||
| #endif | |||
| #include <sys/types.h> | |||
| #include <sys/stat.h> | |||
| @@ -87,7 +86,7 @@ float getdetune(unsigned char type, | |||
| findet = fabs(fdetune / 8192.0f) * 10.0f; | |||
| break; | |||
| case 3: | |||
| cdet = fabs(cdetune * 100); | |||
| cdet = fabs(cdetune * 100.0f); | |||
| findet = powf(10, fabs(fdetune / 8192.0f) * 3.0f) / 10.0f - 0.1f; | |||
| break; | |||
| case 4: | |||
| @@ -138,6 +137,43 @@ void os_sleep(long 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) | |||
| { | |||
| for(int i = 0; i < (int) filename.size(); ++i) { | |||
| @@ -26,9 +26,13 @@ | |||
| #include <string> | |||
| #include <sstream> | |||
| #include <stdint.h> | |||
| #include <algorithm> | |||
| #include "Config.h" | |||
| #include "../globals.h" | |||
| using std::min; | |||
| using std::max; | |||
| //Velocity Sensing function | |||
| extern float VelF(float velocity, unsigned char scaling); | |||
| @@ -48,6 +52,9 @@ void set_realtime(); | |||
| /**Os independent sleep in microsecond*/ | |||
| 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); | |||
| 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(); | |||
| } | |||
| template<class T> | |||
| std::string to_s(T x) | |||
| { | |||
| return stringFrom(x); | |||
| } | |||
| template<class T> | |||
| T stringTo(const char *x) | |||
| { | |||
| @@ -74,12 +87,31 @@ T stringTo(const char *x) | |||
| return ans; | |||
| } | |||
| template<class T> | |||
| T limit(T val, T min, T max) | |||
| { | |||
| 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 | |||
| typedef uint32_t prng_t; | |||
| @@ -105,9 +137,9 @@ inline void sprng(prng_t p) | |||
| * The random generator (0.0f..1.0f) | |||
| */ | |||
| #ifndef INT32_MAX | |||
| # define INT32_MAX (2147483647) | |||
| #define INT32_MAX (2147483647) | |||
| #endif | |||
| #define RND (float(prng()) / float(INT32_MAX)) | |||
| #define RND (prng() / (INT32_MAX * 1.0f)) | |||
| //Linear Interpolation | |||
| 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 | |||
| 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 | |||
| @@ -97,8 +97,8 @@ const char *mxmlElementGetAttr(const mxml_node_t *node, const char *name) | |||
| XMLwrapper::XMLwrapper() | |||
| { | |||
| version.Major = 2; | |||
| version.Minor = 4; | |||
| version.Revision = 4; | |||
| version.Minor = 5; | |||
| version.Revision = 0; | |||
| minimal = true; | |||
| @@ -311,7 +311,7 @@ int XMLwrapper::loadXMLfile(const string &filename) | |||
| mxmlDelete(tree); | |||
| tree = NULL; | |||
| const char *xmldata = doloadfile(filename.c_str()); | |||
| const char *xmldata = doloadfile(filename); | |||
| if(xmldata == NULL) | |||
| 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; | |||
| mxmlElementSetAttr(element, ParamName, ParamValue); | |||
| } | |||
| va_end(variableList); | |||
| } | |||
| 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> | |||
| @@ -28,10 +29,10 @@ using namespace std; | |||
| #include "InMgr.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"; | |||
| audio.handle = NULL; | |||
| @@ -295,7 +296,7 @@ bool AlsaEngine::openAudio() | |||
| /* Two channels (stereo) */ | |||
| 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, | |||
| &audio.sampleRate, NULL); | |||
| @@ -320,7 +321,7 @@ bool AlsaEngine::openAudio() | |||
| /* latency = periodsize * periods / (rate * bytes_per_frame) */ | |||
| snd_pcm_hw_params_set_buffer_size(audio.handle, | |||
| audio.params, | |||
| synth->buffersize); | |||
| synth.buffersize); | |||
| //snd_pcm_hw_params_get_period_size(audio.params, &audio.frames, NULL); | |||
| //snd_pcm_hw_params_get_period_time(audio.params, &val, NULL); | |||
| @@ -352,7 +353,7 @@ void *AlsaEngine::processAudio() | |||
| while(audio.handle) { | |||
| audio.buffer = interleave(getNext()); | |||
| 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) { | |||
| /* EPIPE means underrun */ | |||
| 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 | |||
| @@ -34,7 +35,7 @@ | |||
| class AlsaEngine:public AudioOut, MidiIn | |||
| { | |||
| public: | |||
| AlsaEngine(); | |||
| AlsaEngine(const SYNTH_T &synth); | |||
| ~AlsaEngine(); | |||
| bool Start(); | |||
| @@ -30,8 +30,8 @@ using namespace std; | |||
| #include "../Misc/Master.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() | |||
| @@ -30,7 +30,7 @@ | |||
| class AudioOut:public virtual Engine | |||
| { | |||
| public: | |||
| AudioOut(); | |||
| AudioOut(const SYNTH_T &synth); | |||
| virtual ~AudioOut(); | |||
| /**Sets the Sample Rate of this Output | |||
| @@ -54,6 +54,7 @@ class AudioOut:public virtual Engine | |||
| * (has nsamples sampled at a rate of samplerate)*/ | |||
| const Stereo<float *> getNext(); | |||
| const SYNTH_T &synth; | |||
| int samplerate; | |||
| int bufferSize; | |||
| }; | |||
| @@ -15,19 +15,24 @@ set(zynaddsubfx_nio_SRCS | |||
| Nio.cpp | |||
| ) | |||
| set(zynaddsubfx_nio_lib ) | |||
| set(zynaddsubfx_nio_lib) | |||
| add_definitions(-DOUT_DEFAULT="${DefaultOutput}") | |||
| add_definitions(-DIN_DEFAULT="${DefaultInput}") | |||
| if(JackEnable) | |||
| 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}) | |||
| 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) | |||
| if(PaEnable) | |||
| include_directories(${PORTAUDIO_INCLUDE_DIR}) | |||
| include_directories(${PORTAUDIO_INCLUDE_DIRS}) | |||
| list(APPEND zynaddsubfx_nio_SRCS PaEngine.cpp) | |||
| list(APPEND zynaddsubfx_nio_lib ${PORTAUDIO_LIBRARIES}) | |||
| endif(PaEnable) | |||
| @@ -38,7 +43,7 @@ if(AlsaEnable) | |||
| endif(AlsaEnable) | |||
| if(OssEnable) | |||
| list(APPEND zynaddsubfx_nio_SRCS OssEngine.cpp) | |||
| list(APPEND zynaddsubfx_nio_SRCS OssEngine.cpp OssMultiEngine.cpp) | |||
| endif(OssEnable) | |||
| @@ -1,6 +1,7 @@ | |||
| #include "EngineMgr.h" | |||
| #include <algorithm> | |||
| #include <iostream> | |||
| #include <cassert> | |||
| #include "Nio.h" | |||
| #include "InMgr.h" | |||
| #include "OutMgr.h" | |||
| @@ -9,12 +10,14 @@ | |||
| #include "NulEngine.h" | |||
| #if OSS | |||
| #include "OssEngine.h" | |||
| #include "OssMultiEngine.h" | |||
| #endif | |||
| #if ALSA | |||
| #include "AlsaEngine.h" | |||
| #endif | |||
| #if JACK | |||
| #include "JackEngine.h" | |||
| #include "JackMultiEngine.h" | |||
| #endif | |||
| #if PORTAUDIO | |||
| #include "PaEngine.h" | |||
| @@ -22,29 +25,32 @@ | |||
| using namespace std; | |||
| EngineMgr &EngineMgr::getInstance() | |||
| EngineMgr &EngineMgr::getInstance(const SYNTH_T *synth) | |||
| { | |||
| static EngineMgr instance; | |||
| static EngineMgr instance(synth); | |||
| 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) | |||
| engines.push_back(defaultEng); | |||
| #if OSS | |||
| engines.push_back(new OssEngine()); | |||
| engines.push_back(new OssEngine(*synth)); | |||
| engines.push_back(new OssMultiEngine(*synth)); | |||
| #endif | |||
| #if ALSA | |||
| engines.push_back(new AlsaEngine()); | |||
| engines.push_back(new AlsaEngine(*synth)); | |||
| #endif | |||
| #if JACK | |||
| engines.push_back(new JackEngine()); | |||
| engines.push_back(new JackEngine(*synth)); | |||
| engines.push_back(new JackMultiEngine(*synth)); | |||
| #endif | |||
| #if PORTAUDIO | |||
| engines.push_back(new PaEngine()); | |||
| engines.push_back(new PaEngine(*synth)); | |||
| #endif | |||
| defaultOut = dynamic_cast<AudioOut *>(defaultEng); | |||
| @@ -9,11 +9,12 @@ | |||
| class MidiIn; | |||
| class AudioOut; | |||
| class OutMgr; | |||
| struct SYNTH_T; | |||
| /**Container/Owner of the long lived Engines*/ | |||
| class EngineMgr | |||
| { | |||
| public: | |||
| static EngineMgr &getInstance(); | |||
| static EngineMgr &getInstance(const SYNTH_T *synth=NULL); | |||
| ~EngineMgr(); | |||
| /**Gets requested engine | |||
| @@ -38,6 +39,6 @@ class EngineMgr | |||
| AudioOut *defaultOut; | |||
| MidiIn *defaultIn; | |||
| private: | |||
| EngineMgr(); | |||
| EngineMgr(const SYNTH_T *synth); | |||
| }; | |||
| #endif | |||
| @@ -2,10 +2,15 @@ | |||
| #include "MidiIn.h" | |||
| #include "EngineMgr.h" | |||
| #include "../Misc/Master.h" | |||
| #include "../Misc/Part.h" | |||
| #include "../Misc/MiddleWare.h" | |||
| #include <rtosc/thread-link.h> | |||
| #include <iostream> | |||
| using namespace std; | |||
| extern MiddleWare *middleware; | |||
| ostream &operator<<(ostream &out, const MidiEvent &ev) | |||
| { | |||
| switch(ev.type) { | |||
| @@ -41,7 +46,7 @@ InMgr &InMgr::getInstance() | |||
| } | |||
| InMgr::InMgr() | |||
| :queue(100), master(Master::getInstance()) | |||
| :queue(100), master(NULL) | |||
| { | |||
| current = NULL; | |||
| work.init(PTHREAD_PROCESS_PRIVATE, 0); | |||
| @@ -76,24 +81,27 @@ void InMgr::flush(unsigned frameStart, unsigned frameStop) | |||
| switch(ev.type) { | |||
| case M_NOTE: | |||
| dump.dumpnote(ev.channel, ev.num, ev.value); | |||
| if(ev.value) | |||
| master.noteOn(ev.channel, ev.num, ev.value); | |||
| master->noteOn(ev.channel, ev.num, ev.value); | |||
| else | |||
| master.noteOff(ev.channel, ev.num); | |||
| master->noteOff(ev.channel, ev.num); | |||
| break; | |||
| 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; | |||
| 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; | |||
| case M_PRESSURE: | |||
| master.polyphonicAftertouch(ev.channel, ev.num, ev.value); | |||
| master->polyphonicAftertouch(ev.channel, ev.num, ev.value); | |||
| break; | |||
| } | |||
| } | |||
| @@ -136,6 +144,11 @@ string InMgr::getSource() const | |||
| MidiIn *InMgr::getIn(string name) | |||
| { | |||
| EngineMgr &eng = EngineMgr::getInstance(); | |||
| EngineMgr &eng = EngineMgr::getInstance(NULL); | |||
| return dynamic_cast<MidiIn *>(eng.getEng(name)); | |||
| } | |||
| void InMgr::setMaster(Master *master_) | |||
| { | |||
| master = master_; | |||
| } | |||
| @@ -40,6 +40,8 @@ class InMgr | |||
| std::string getSource() const; | |||
| void setMaster(class Master *master); | |||
| friend class EngineMgr; | |||
| private: | |||
| InMgr(); | |||
| @@ -49,7 +51,7 @@ class InMgr | |||
| class MidiIn * current; | |||
| /**the link to the rest of zyn*/ | |||
| class Master & master; | |||
| class Master *master; | |||
| }; | |||
| #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 <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 <sys/stat.h> | |||
| #include <cassert> | |||
| #include <cstring> | |||
| #include <unistd.h> // access() | |||
| #include <fstream> // std::istream | |||
| #include "Nio.h" | |||
| #include "OutMgr.h" | |||
| #include "InMgr.h" | |||
| #include "Misc/Util.h" | |||
| #include "JackEngine.h" | |||
| @@ -34,8 +44,8 @@ using namespace std; | |||
| extern char *instance_name; | |||
| JackEngine::JackEngine() | |||
| :AudioOut(), jackClient(NULL) | |||
| JackEngine::JackEngine(const SYNTH_T &synth) | |||
| :AudioOut(synth), jackClient(NULL) | |||
| { | |||
| name = "JACK"; | |||
| audio.jackSamplerate = 0; | |||
| @@ -58,6 +68,9 @@ bool JackEngine::connectServer(string server) | |||
| string postfix = Nio::getPostfix(); | |||
| if(!postfix.empty()) | |||
| clientname += "_" + postfix; | |||
| if(Nio::pidInClientName) | |||
| clientname += "_" + os_pid_as_padded_string(); | |||
| jack_status_t jackstatus; | |||
| bool use_server_name = server.size() && server.compare("default") != 0; | |||
| jack_options_t jopts = (jack_options_t) | |||
| @@ -93,12 +106,11 @@ bool JackEngine::connectJack() | |||
| connectServer(""); | |||
| if(NULL != jackClient) { | |||
| setBufferSize(jack_get_buffer_size(jackClient)); | |||
| int chk; | |||
| jack_set_error_function(_errorCallback); | |||
| jack_set_info_function(_infoCallback); | |||
| if(jack_set_buffer_size_callback(jackClient, _bufferSizeCallback, this)) | |||
| 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; | |||
| if(jack_set_process_callback(jackClient, _processCallback, this)) { | |||
| cerr << "Error, JackEngine failed to set process callback" << endl; | |||
| @@ -213,6 +225,12 @@ bool JackEngine::openAudio() | |||
| cerr << "Warning, No outputs to autoconnect to" << endl; | |||
| } | |||
| 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; | |||
| } | |||
| else | |||
| @@ -226,10 +244,19 @@ void JackEngine::stopAudio() | |||
| for(int i = 0; i < 2; ++i) { | |||
| jack_port_t *port = audio.ports[i]; | |||
| audio.ports[i] = NULL; | |||
| if(NULL != port) | |||
| if(jackClient != NULL && NULL != port) | |||
| jack_port_unregister(jackClient, port); | |||
| } | |||
| 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()) | |||
| disconnectJack(); | |||
| } | |||
| @@ -293,6 +320,20 @@ int JackEngine::processCallback(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) { | |||
| audio.portBuffs[port] = | |||
| (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 | |||
| @@ -34,7 +36,7 @@ typedef jack_default_audio_sample_t jsample_t; | |||
| class JackEngine:public AudioOut, MidiIn | |||
| { | |||
| public: | |||
| JackEngine(); | |||
| JackEngine(const SYNTH_T &synth); | |||
| ~JackEngine() { } | |||
| bool Start(); | |||
| @@ -78,6 +80,9 @@ class JackEngine:public AudioOut, MidiIn | |||
| jack_port_t *ports[2]; | |||
| jsample_t *portBuffs[2]; | |||
| } audio; | |||
| struct osc { | |||
| jack_port_t *oscport; | |||
| } osc; | |||
| struct midi { | |||
| jack_port_t *inport; | |||
| 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::endl; | |||
| #ifndef IN_DEFAULT | |||
| #define IN_DEFAULT "NULL" | |||
| #endif | |||
| #ifndef OUT_DEFAULT | |||
| #define OUT_DEFAULT "NULL" | |||
| #endif | |||
| InMgr *in = NULL; | |||
| OutMgr *out = NULL; | |||
| EngineMgr *eng = NULL; | |||
| 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 | |||
| 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() | |||
| { | |||
| init(); | |||
| return eng->start(); | |||
| if(eng) | |||
| return eng->start(); | |||
| else | |||
| return false; | |||
| } | |||
| void Nio::stop() | |||
| @@ -105,6 +118,10 @@ string Nio::getSink() | |||
| #include <jack/jack.h> | |||
| 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", | |||
| JackNoStartServer, 0); | |||
| if(client) { | |||
| @@ -117,6 +134,12 @@ void Nio::preferedSampleRate(unsigned &) | |||
| {} | |||
| #endif | |||
| void Nio::masterSwap(Master *master) | |||
| { | |||
| in->setMaster(master); | |||
| out->setMaster(master); | |||
| } | |||
| void Nio::waveNew(class WavFile *wave) | |||
| { | |||
| out->wave->newFile(wave); | |||
| @@ -4,13 +4,15 @@ | |||
| #include <set> | |||
| class WavFile; | |||
| class Master; | |||
| struct SYNTH_T; | |||
| /**Interface to Nio Subsystem | |||
| * | |||
| * Should be only externally included header */ | |||
| namespace Nio | |||
| { | |||
| void init(void); | |||
| void init(const SYNTH_T &synth, Master *master); | |||
| bool start(void); | |||
| void stop(void); | |||
| @@ -32,6 +34,8 @@ namespace Nio | |||
| //Get the prefered sample rate from jack (if running) | |||
| void preferedSampleRate(unsigned &rate); | |||
| //Complete Master Swaps to ONLY BE CALLED FROM RT CONTEXT | |||
| void masterSwap(Master *master); | |||
| //Wave writing | |||
| void waveNew(class WavFile *wave); | |||
| @@ -40,6 +44,7 @@ namespace Nio | |||
| void waveEnd(void); | |||
| extern bool autoConnect; | |||
| extern bool pidInClientName; | |||
| extern std::string defaultSource; | |||
| extern std::string defaultSink; | |||
| }; | |||
| @@ -28,8 +28,8 @@ | |||
| using namespace std; | |||
| NulEngine::NulEngine() | |||
| :AudioOut(), pThread(NULL) | |||
| NulEngine::NulEngine(const SYNTH_T &synth_) | |||
| :AudioOut(synth_), pThread(NULL) | |||
| { | |||
| name = "NULL"; | |||
| playing_until.tv_sec = 0; | |||
| @@ -62,8 +62,8 @@ void *NulEngine::AudioThread() | |||
| if(remaining < 0) | |||
| 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) | |||
| playing_until.tv_usec -= remaining; | |||
| playing_until.tv_sec += playing_until.tv_usec / 1000000; | |||
| @@ -32,7 +32,7 @@ | |||
| class NulEngine:public AudioOut, MidiIn | |||
| { | |||
| public: | |||
| NulEngine(); | |||
| NulEngine(const SYNTH_T &synth_); | |||
| ~NulEngine(); | |||
| bool Start(); | |||
| @@ -28,71 +28,254 @@ | |||
| #include <stdlib.h> | |||
| #include <stdio.h> | |||
| #include <fcntl.h> | |||
| #include <errno.h> | |||
| #include <sys/soundcard.h> | |||
| #include <sys/stat.h> | |||
| #include <sys/ioctl.h> | |||
| #include <unistd.h> | |||
| #include <iostream> | |||
| #include <signal.h> | |||
| #include "InMgr.h" | |||
| 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"; | |||
| midi.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() | |||
| { | |||
| Stop(); | |||
| delete [] audio.smps; | |||
| delete [] audio.smps.ps32; | |||
| } | |||
| bool OssEngine::openAudio() | |||
| { | |||
| int x; | |||
| if(audio.handle != -1) | |||
| return true; //already open | |||
| int snd_bitsize = 16; | |||
| int snd_fragment = 0x00080009; //fragment size (?); | |||
| int snd_fragment; | |||
| 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) { | |||
| cerr << "ERROR - I can't open the " | |||
| << device << '.' << endl; | |||
| return false; | |||
| } | |||
| 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_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; | |||
| error: | |||
| close(audio.handle); | |||
| audio.handle = -1; | |||
| return false; | |||
| } | |||
| void OssEngine::stopAudio() | |||
| @@ -102,12 +285,12 @@ void OssEngine::stopAudio() | |||
| return; | |||
| audio.handle = -1; | |||
| if(!getMidiEn() && engThread) | |||
| pthread_join(*engThread, NULL); | |||
| delete engThread; | |||
| engThread = NULL; | |||
| /* close handle first, so that write() exits */ | |||
| close(handle); | |||
| pthread_join(*audioThread, NULL); | |||
| delete audioThread; | |||
| audioThread = NULL; | |||
| } | |||
| bool OssEngine::Start() | |||
| @@ -165,19 +348,22 @@ bool OssEngine::openMidi() | |||
| if(handle != -1) | |||
| 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) | |||
| return false; | |||
| 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; | |||
| } | |||
| @@ -190,96 +376,110 @@ void OssEngine::stopMidi() | |||
| midi.handle = -1; | |||
| if(!getAudioEn() && engThread) { | |||
| pthread_join(*engThread, NULL); | |||
| delete engThread; | |||
| engThread = NULL; | |||
| } | |||
| /* close handle first, so that read() exits */ | |||
| 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(); | |||
| 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) | |||
| l = 1.0f; | |||
| if(r < -1.0f) | |||
| r = -1.0f; | |||
| else | |||
| if(r < -1.0f) | |||
| r = -1.0f; | |||
| else | |||
| if(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); | |||
| 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; | |||
| } | |||