├── screenshots └── plugin_qtractor.png ├── minunit.h ├── manifest.ttl ├── simplearpeggiator.h ├── TODO ├── Simple_Apreggiator_presets.lv2 ├── manifest.ttl └── presets.ttl ├── test.c ├── arpeggiator.h ├── Makefile ├── README.md ├── simplearpeggiator.ttl ├── arpeggiator.c ├── simplearpeggiator_gui_qt5.cpp ├── LICENSE └── simplearpeggiator.c /screenshots/plugin_qtractor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johanberntsson/simple-arpeggiator-lv2/HEAD/screenshots/plugin_qtractor.png -------------------------------------------------------------------------------- /minunit.h: -------------------------------------------------------------------------------- 1 | // Unit test for C. See http://www.jera.com/techinfo/jtns/jtn002.html 2 | 3 | #define mu_assert(message, test) do { if (!(test)) return message; } while (0) 4 | #define mu_run_test(test) do { char *message = test(); tests_run++; \ 5 | if (message) return message; } while (0) 6 | extern int tests_run; 7 | -------------------------------------------------------------------------------- /manifest.ttl: -------------------------------------------------------------------------------- 1 | @prefix lv2: . 2 | @prefix rdfs: . 3 | @prefix ui: . 4 | 5 | 6 | a lv2:Plugin ; 7 | lv2:binary ; 8 | rdfs:seeAlso . 9 | 10 | 11 | a ui:Qt5UI ; 12 | ui:binary ; 13 | rdfs:seeAlso . 14 | 15 | -------------------------------------------------------------------------------- /simplearpeggiator.h: -------------------------------------------------------------------------------- 1 | #define SIMPLEARPEGGIATOR_URI \ 2 | "https://github.com/johanberntsson/simple-arpeggiator-lv2" 3 | 4 | #define SIMPLEARPEGGIATOR_N_PORTS 9 5 | /* has to correspond to port index numbers in simplearpeggiator.ttl */ 6 | enum { 7 | SIMPLEARPEGGIATOR_IN = 0, 8 | SIMPLEARPEGGIATOR_OUT = 1, 9 | SIMPLEARPEGGIATOR_CHORD = 2, 10 | SIMPLEARPEGGIATOR_RANGE = 3, 11 | SIMPLEARPEGGIATOR_TIME = 4, 12 | SIMPLEARPEGGIATOR_GATE = 5, 13 | SIMPLEARPEGGIATOR_CYCLE = 6, 14 | SIMPLEARPEGGIATOR_SKIP = 7, 15 | SIMPLEARPEGGIATOR_DIR = 8 16 | } PortIndex; 17 | 18 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Feature requests/future plans: 2 | - add free/sort/synch modes 3 | 4 | Known bugs: 5 | - restart doesn't always start exactly on the beat (need synch mode) 6 | 7 | Fixed: 8 | 9 | - add support for cycle (skip every n step), and skip (randomly miss notes) 10 | - add up/down/up-down directions 11 | - starts one arp step too late 12 | - preset load/save from the GUI (with some kind of list for fast selection) 13 | - cannot restart after pause in the middle of a note (playback) 14 | Not an error. If you start playback in a middle on a note there is no note-on command 15 | - bpm changes not detected properly when changed in a sequencer session 16 | - need to add midi note off for gate 17 | - need to add major and minor chords 18 | 19 | -------------------------------------------------------------------------------- /Simple_Apreggiator_presets.lv2/manifest.ttl: -------------------------------------------------------------------------------- 1 | @prefix atom: . 2 | @prefix lv2: . 3 | @prefix pset: . 4 | @prefix rdf: . 5 | @prefix rdfs: . 6 | @prefix state: . 7 | @prefix xsd: . 8 | 9 | 10 | lv2:appliesTo ; 11 | a pset:Preset ; 12 | rdfs:seeAlso . 13 | 14 | 15 | lv2:appliesTo ; 16 | a pset:Preset ; 17 | rdfs:seeAlso . 18 | 19 | 20 | lv2:appliesTo ; 21 | a pset:Preset ; 22 | rdfs:seeAlso . 23 | 24 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "minunit.h" 4 | 5 | #include "arpeggiator.c" 6 | 7 | int tests_run = 0; 8 | 9 | int foo = 7; 10 | 11 | static char* test_note_as_fraction_of_bar() { 12 | // test notes a fraction of a bar in different time signatures 13 | float d = 0.001; 14 | setTime(NOTE_1_1); 15 | mu_assert("error, 1/1 in 4/4", fabs(note_as_fraction_of_bar(4, 4) - 1.0) < d); 16 | setTime(NOTE_1_2); 17 | mu_assert("error, 1/2 in 3/4", fabs(note_as_fraction_of_bar(3, 4) - 0.666) < d); 18 | setTime(NOTE_1_8); 19 | mu_assert("error, 1/8 in 4/4", fabs(note_as_fraction_of_bar(4, 4) - 0.125) < d); 20 | setTime(NOTE_1_32); 21 | mu_assert("error, 1/32 in 4/4", fabs(note_as_fraction_of_bar(4, 4) - 0.03125) < d); 22 | setTime(NOTE_1_8); 23 | mu_assert("error, 1/8 in 3/4", fabs(note_as_fraction_of_bar(3, 4) - 0.1666) < d); 24 | return 0; 25 | } 26 | 27 | static char* all_tests() { 28 | mu_run_test(test_note_as_fraction_of_bar); 29 | return 0; 30 | } 31 | 32 | int main(int argc, char **argv) { 33 | char *result = all_tests(); 34 | if (result != 0) { 35 | printf("%s\n", result); 36 | } 37 | else { 38 | printf("ALL TESTS PASSED\n"); 39 | } 40 | printf("Tests run: %d\n", tests_run); 41 | 42 | return result != 0; 43 | } 44 | -------------------------------------------------------------------------------- /arpeggiator.h: -------------------------------------------------------------------------------- 1 | /* 2 | SimpleArpeggiator LV2 Plugin 3 | Copyright 2017 Johan Berntsson 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | enum chordtype { 19 | OCTAVE = 0, 20 | MAJOR = 1, 21 | MINOR = 2, 22 | CHORD_ERROR 23 | }; 24 | 25 | enum timetype { 26 | NOTE_1_1 = 0, 27 | NOTE_1_2 = 1, 28 | NOTE_1_4 = 2, 29 | NOTE_1_8 = 3, 30 | NOTE_1_16 = 4, 31 | NOTE_1_32 = 5, 32 | NOTE_ERROR 33 | }; 34 | 35 | enum dirtype { 36 | DIR_UP = 0, 37 | DIR_DOWN = 1, 38 | DIR_UPDOWN = 2, 39 | DIR_ERROR 40 | }; 41 | 42 | float getGate(); 43 | 44 | int setChord(enum chordtype chord); 45 | int setRange(int range); 46 | int setTime(enum timetype time); 47 | int setGate(float gate); 48 | int setCycle(int range); 49 | int setSkip(float gate); 50 | int setDir(enum dirtype time); 51 | 52 | 53 | void resetArpeggio(); 54 | void updateArpeggioNotes(); 55 | uint8_t nextNote(uint8_t base_note); 56 | 57 | float note_as_fraction_of_bar(int beat_unit, int beats_per_bar); 58 | 59 | 60 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUNDLE = simplearpeggiator.lv2 2 | INSTALL_ROOT_DIR = /usr/lib/lv2 3 | INSTALL_LOCAL_DIR = $(HOME)/.lv2 4 | 5 | all: $(BUNDLE) 6 | 7 | gui: install 8 | jalv.qt5 https://github.com/johanberntsson/simple-arpeggiator-lv2 9 | 10 | test-main: test.c 11 | gcc test.c -lm -o test 12 | 13 | test: test-main 14 | ./test 15 | 16 | $(BUNDLE): manifest.ttl simplearpeggiator.ttl simplearpeggiator.so simplearpeggiator_gui_qt5.so 17 | rm -rf $(BUNDLE) 18 | mkdir $(BUNDLE) 19 | cp manifest.ttl simplearpeggiator.ttl simplearpeggiator.so simplearpeggiator_gui_qt5.so $(BUNDLE) 20 | 21 | simplearpeggiator.o: simplearpeggiator.c simplearpeggiator.h 22 | gcc -c -fPIC -DPIC simplearpeggiator.c 23 | 24 | arpeggiator.o: arpeggiator.c arpeggiator.h 25 | gcc -c -fPIC -DPIC arpeggiator.c 26 | 27 | simplearpeggiator.so: simplearpeggiator.o arpeggiator.o 28 | gcc -shared -fPIC -DPIC arpeggiator.o simplearpeggiator.o `pkg-config --cflags --libs lv2-plugin` -o simplearpeggiator.so 29 | 30 | simplearpeggiator_gui_qt5.o: simplearpeggiator_gui_qt5.moc.cpp 31 | 32 | simplearpeggiator_gui_qt5.moc.cpp: simplearpeggiator_gui_qt5.cpp 33 | moc $(DEFINES) $(INCPATH) -i $< -o $@ 34 | 35 | simplearpeggiator_gui_qt5.so: simplearpeggiator_gui_qt5.cpp simplearpeggiator_gui_qt5.moc.cpp simplearpeggiator.h 36 | g++ $< -o $@ -shared -fPIC -Wl,--no-undefined `pkg-config --cflags --libs Qt5Core Qt5Gui Qt5Widgets` 37 | 38 | install: $(BUNDLE) 39 | ifeq ($(shell whoami), root) 40 | mkdir -p $(INSTALL_ROOT_DIR) 41 | rm -rf $(INSTALL_ROOT_DIR)/$(BUNDLE) 42 | cp -R $(BUNDLE) $(INSTALL_ROOT_DIR) 43 | else 44 | mkdir -p $(INSTALL_LOCAL_DIR) 45 | rm -rf $(INSTALL_LOCAL_DIR)/$(BUNDLE) 46 | cp -R $(BUNDLE) $(INSTALL_LOCAL_DIR) 47 | endif 48 | mkdir -p $(INSTALL_LOCAL_DIR) 49 | cp -R Simple_Apreggiator_presets.lv2 $(INSTALL_LOCAL_DIR) 50 | 51 | uninstall: 52 | ifeq ($(shell whoami), root) 53 | rm -rf $(INSTALL_ROOT_DIR)/$(BUNDLE) 54 | else 55 | rm -rf $(INSTALL_LOCAL_DIR)/$(BUNDLE) 56 | endif 57 | rm -rf $(INSTALL_LOCAL_DIR)/Simple_Apreggiator_presets.lv2 58 | 59 | clean: 60 | rm -rf $(BUNDLE) *.o *.so *.moc.cpp 61 | 62 | -------------------------------------------------------------------------------- /Simple_Apreggiator_presets.lv2/presets.ttl: -------------------------------------------------------------------------------- 1 | @prefix atom: . 2 | @prefix lv2: . 3 | @prefix pset: . 4 | @prefix rdf: . 5 | @prefix rdfs: . 6 | @prefix state: . 7 | @prefix xsd: . 8 | 9 | 10 | a pset:Preset ; 11 | lv2:appliesTo ; 12 | rdfs:label "basic-bass" ; 13 | lv2:port [ 14 | lv2:symbol "chordtype" ; 15 | pset:value 0.0 16 | ] , [ 17 | lv2:symbol "cycle" ; 18 | pset:value 0.0 19 | ] , [ 20 | lv2:symbol "direction" ; 21 | pset:value 0.0 22 | ] , [ 23 | lv2:symbol "gate" ; 24 | pset:value 100.0 25 | ] , [ 26 | lv2:symbol "range" ; 27 | pset:value 2.0 28 | ] , [ 29 | lv2:symbol "skip" ; 30 | pset:value 0.0 31 | ] , [ 32 | lv2:symbol "time" ; 33 | pset:value 3.0 34 | ] . 35 | 36 | 37 | a pset:Preset ; 38 | lv2:appliesTo ; 39 | rdfs:label "fast-major-chords" ; 40 | lv2:port [ 41 | lv2:symbol "chordtype" ; 42 | pset:value 1.0 43 | ] , [ 44 | lv2:symbol "cycle" ; 45 | pset:value 0.0 46 | ] , [ 47 | lv2:symbol "direction" ; 48 | pset:value 0.0 49 | ] , [ 50 | lv2:symbol "gate" ; 51 | pset:value 100.0 52 | ] , [ 53 | lv2:symbol "range" ; 54 | pset:value 3.0 55 | ] , [ 56 | lv2:symbol "skip" ; 57 | pset:value 0.0 58 | ] , [ 59 | lv2:symbol "time" ; 60 | pset:value 4.0 61 | ] . 62 | 63 | 64 | a pset:Preset ; 65 | lv2:appliesTo ; 66 | rdfs:label "complex-random-chords" ; 67 | lv2:port [ 68 | lv2:symbol "chordtype" ; 69 | pset:value 1.0 70 | ] , [ 71 | lv2:symbol "cycle" ; 72 | pset:value 1.0 73 | ] , [ 74 | lv2:symbol "direction" ; 75 | pset:value 2.0 76 | ] , [ 77 | lv2:symbol "gate" ; 78 | pset:value 65.0 79 | ] , [ 80 | lv2:symbol "range" ; 81 | pset:value 3.0 82 | ] , [ 83 | lv2:symbol "skip" ; 84 | pset:value 23.0 85 | ] , [ 86 | lv2:symbol "time" ; 87 | pset:value 4.0 88 | ] . 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A simple arpeggiator LV2 module for Linux DAWs 2 | === 3 | 4 | This arpeggiator is inspired by the LMMS standard arpeggiator effect. It can be added to effect chains in hosts such as QTractor to provide interesting bass lines and similar effects. 5 | 6 | ![SimpleArpeggiator in Qtractor](https://github.com/johanberntsson/simple-arpeggiator-lv2/blob/master/screenshots/plugin_qtractor.png) 7 | 8 | INSTALLATION 9 | ------------ 10 | 11 | Build the plugin from source with "make". To install the plugin locally (in ~/.lv2) type "make install". To install the plugin system-wide (in /usr/local/lv2) you instead need to type "sudo make install". In either case a preset bank is also installed locally in ~/.lv2 12 | 13 | To remove the plugin type "make uninstall" for local plugins, and "sudo make uninstall" for system-wide plugins. 14 | 15 | USAGE 16 | ----- 17 | 18 | These parameters are supported: 19 | 20 | * **chord** three types are supported: octave, major, and minor 21 | * **range** the arpeggio range in octaves 22 | * **time** set the length of each arpeggio note, for instance 1/8ths. 23 | * **gate** the percent of a whole apreggio note that should be played. Setting it to less than 100% can create cool staccato effects 24 | * **cycle** cycle will jump over the 1-6th step in the arpeggio 25 | * **skip** skip will cause the arpeggio to pause randomly if set to more than 0% 26 | * **dir** controls how the arpeggio is played: up, down, or up-down 27 | 28 | CODE 29 | ---- 30 | 31 | The code is divided into these logical parts: 32 | 33 | **Plugin interface**: 34 | manifest.ttl and simplearpeggiator.ttl define the input, output 35 | and control ports. A host like QTractor will create a user interface 36 | from the control port information which allows the user to adjust 37 | the arpeggiator parameters. 38 | 39 | **Plugin**: 40 | simplearpeggiator.c handles the lifecycle of the plugin, 41 | including instantiation, updating of control parameter information 42 | and providing access of the state information to the host application 43 | for permanent storage and recovery. 44 | 45 | Events are processed in the run() function, and routed to 46 | update_midi() for midi on/off messages, and update_time() for 47 | time synchronization messages. 48 | 49 | **Arpeggiator**: 50 | The actual arpeggiator functionality is all in arpeggiator.c, which 51 | is called from simplearpeggiator.c. This allows the apreggiator to 52 | be easily reused in future applications, such as other plugin formats 53 | or stand-alone applications. 54 | 55 | **Graphical User Interface**: 56 | The optional GUI is implemented in Qt5. The implementation files are simplearpeggiator_gui_qt5.cpp and simplearpeggiator_gui_qt5.h. 57 | 58 | RESOURCES 59 | --------- 60 | Useful information for LV2 development: 61 | 62 | * LV2 spec: http://lv2plug.in/ns/lv2core/ 63 | * Good examples: http://lv2plug.in/book/ 64 | * Outdated but useful info: http://ll-plugins.nongnu.org/lv2pftci/ 65 | * GUI examples: https://github.com/badosu/BadAmp 66 | * GUI with fluid: http://mountainbikesandtrombones.blogspot.com.au/2014/08/making-lv2-plugin-gui-yes-in-inkscape.html 67 | * LV2 present handling: https://freesoftwaremusic.wordpress.com/2014/08/11/lv2-presets/ 68 | -------------------------------------------------------------------------------- /simplearpeggiator.ttl: -------------------------------------------------------------------------------- 1 | @prefix atom: . 2 | @prefix doap: . 3 | @prefix lv2: . 4 | @prefix urid: . 5 | @prefix midi: . 6 | @prefix time: . 7 | @prefix epp: . 8 | @prefix rdf: . 9 | @prefix rdfs: . 10 | @prefix units: . 11 | @prefix state: . 12 | @prefix ui: . 13 | 14 | 15 | a ui:Qt5UI; 16 | ui:binary ; 17 | ui:requiredFeature ui:makeResident . 18 | 19 | 20 | a lv2:Plugin ; 21 | doap:name "Simple Apreggiator" ; 22 | doap:license ; 23 | lv2:project ; 24 | lv2:requiredFeature urid:map ; 25 | lv2:optionalFeature lv2:hardRTCapable ; 26 | lv2:extensionData state:interface ; 27 | ui:ui 28 | lv2:port [ 29 | a lv2:InputPort , 30 | atom:AtomPort ; 31 | atom:bufferType atom:Sequence ; 32 | atom:supports time:Position ; 33 | atom:supports midi:MidiEvent ; 34 | lv2:index 0 ; 35 | lv2:symbol "in" ; 36 | lv2:name "In" 37 | ] , [ 38 | a lv2:OutputPort , 39 | atom:AtomPort ; 40 | atom:bufferType atom:Sequence ; 41 | atom:supports midi:MidiEvent ; 42 | lv2:index 1 ; 43 | lv2:symbol "out" ; 44 | lv2:name "Out" 45 | ] , [ 46 | a lv2:InputPort , 47 | lv2:ControlPort ; 48 | lv2:index 2 ; 49 | lv2:symbol "chordtype" ; 50 | lv2:name "Chord Type" ; 51 | lv2:portProperty epp:hasStrictBounds ; 52 | lv2:portProperty lv2:integer ; 53 | lv2:portProperty lv2:enumeration ; 54 | lv2:scalePoint [ rdfs:label "Octave"; rdf:value 0 ] ; 55 | lv2:scalePoint [ rdfs:label "Major"; rdf:value 1 ] ; 56 | lv2:scalePoint [ rdfs:label "Minor"; rdf:value 2 ] ; 57 | lv2:default 0.00000 ; 58 | lv2:minimum 0.00000 ; 59 | lv2:maximum 2.0000 ; 60 | ] , [ 61 | a lv2:InputPort , 62 | lv2:ControlPort ; 63 | lv2:index 3 ; 64 | lv2:symbol "range" ; 65 | lv2:name "Range" ; 66 | lv2:portProperty epp:hasStrictBounds ; 67 | lv2:portProperty lv2:integer ; 68 | lv2:default 2.00000 ; 69 | lv2:minimum 1.00000 ; 70 | lv2:maximum 9.0000 ; 71 | ] , [ 72 | a lv2:InputPort , 73 | lv2:ControlPort ; 74 | lv2:index 4 ; 75 | lv2:symbol "time" ; 76 | lv2:name "Time" ; 77 | lv2:portProperty epp:hasStrictBounds ; 78 | lv2:portProperty lv2:integer ; 79 | lv2:portProperty lv2:enumeration ; 80 | lv2:scalePoint [ rdfs:label "1/1"; rdf:value 0 ] ; 81 | lv2:scalePoint [ rdfs:label "1/2"; rdf:value 1 ] ; 82 | lv2:scalePoint [ rdfs:label "1/4"; rdf:value 2 ] ; 83 | lv2:scalePoint [ rdfs:label "1/8"; rdf:value 3 ] ; 84 | lv2:scalePoint [ rdfs:label "1/16"; rdf:value 4 ] ; 85 | lv2:scalePoint [ rdfs:label "1/32"; rdf:value 5 ] ; 86 | lv2:default 3.00000 ; 87 | lv2:minimum 0.00000 ; 88 | lv2:maximum 5.0000 ; 89 | ] , [ 90 | a lv2:InputPort , 91 | lv2:ControlPort ; 92 | lv2:index 5 ; 93 | lv2:symbol "gate" ; 94 | lv2:name "Gate (%)" ; 95 | lv2:default 100.0 ; 96 | lv2:minimum 0.0 ; 97 | lv2:maximum 100.0 ; 98 | units:unit units:pc ; 99 | lv2:scalePoint [ 100 | rdfs:label "0" ; 101 | rdf:value 0.0 102 | ] , [ 103 | rdfs:label "50" ; 104 | rdf:value 50.0 105 | ] , [ 106 | rdfs:label "100" ; 107 | rdf:value 100.0 108 | ] 109 | ] , [ 110 | a lv2:InputPort , 111 | lv2:ControlPort ; 112 | lv2:index 6 ; 113 | lv2:symbol "cycle" ; 114 | lv2:name "Cycle" ; 115 | lv2:default 0.0 ; 116 | lv2:minimum 0.0 ; 117 | lv2:maximum 6.0 ; 118 | # units:unit units:db ; 119 | lv2:scalePoint [ 120 | rdfs:label "0" ; 121 | rdf:value 0.0 122 | ] , [ 123 | rdfs:label "3" ; 124 | rdf:value 3.0 125 | ] , [ 126 | rdfs:label "6" ; 127 | rdf:value 6.0 128 | ] 129 | ] , [ 130 | a lv2:InputPort , 131 | lv2:ControlPort ; 132 | lv2:index 7 ; 133 | lv2:symbol "skip" ; 134 | lv2:name "Skip (%)" ; 135 | lv2:default 0.0 ; 136 | lv2:minimum 0.0 ; 137 | lv2:maximum 100.0 ; 138 | units:unit units:pc ; 139 | lv2:scalePoint [ 140 | rdfs:label "0" ; 141 | rdf:value 0.0 142 | ] , [ 143 | rdfs:label "50" ; 144 | rdf:value 50.0 145 | ] , [ 146 | rdfs:label "100" ; 147 | rdf:value 100.0 148 | ] 149 | ] , [ 150 | a lv2:InputPort , 151 | lv2:ControlPort ; 152 | lv2:index 8 ; 153 | lv2:symbol "direction" ; 154 | lv2:name "Direction" ; 155 | lv2:portProperty epp:hasStrictBounds ; 156 | lv2:portProperty lv2:integer ; 157 | lv2:portProperty lv2:enumeration ; 158 | lv2:scalePoint [ rdfs:label "Up"; rdf:value 0 ] ; 159 | lv2:scalePoint [ rdfs:label "Down"; rdf:value 1 ] ; 160 | lv2:scalePoint [ rdfs:label "Up-Down"; rdf:value 2 ] ; 161 | lv2:default 0.00000 ; 162 | lv2:minimum 0.00000 ; 163 | lv2:maximum 2.0000 ; 164 | ] . 165 | 166 | -------------------------------------------------------------------------------- /arpeggiator.c: -------------------------------------------------------------------------------- 1 | /* 2 | SimpleArpeggiator LV2 Plugin 3 | Copyright 2017 Johan Berntsson 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "arpeggiator.h" 24 | 25 | typedef struct { 26 | enum chordtype chord; 27 | int range; 28 | enum timetype time; 29 | float gate; 30 | int cycle; 31 | float skip; 32 | enum dirtype dir; 33 | 34 | uint32_t note_index; 35 | uint32_t arpeggio_length; // number of arpeggio notes 36 | uint8_t arpeggio_notes[2*10*3]; // max octaves*max notes/octave*2(up-down) 37 | } Arpeggiator; 38 | 39 | Arpeggiator arp_state; 40 | 41 | float getGate() { 42 | return arp_state.gate; 43 | } 44 | 45 | /* Setters: return 0 if no change, -1 if new value set */ 46 | int setChord(enum chordtype chord) { 47 | if(arp_state.chord != chord) { 48 | arp_state.chord = chord; 49 | return -1; 50 | } 51 | return 0; 52 | } 53 | 54 | int setRange(int range) { 55 | if(arp_state.range != range) { 56 | arp_state.range = range; 57 | return -1; 58 | } 59 | return 0; 60 | } 61 | 62 | int setTime(enum timetype time) { 63 | if(arp_state.time != time) { 64 | arp_state.time = time; 65 | return -1; 66 | } 67 | return 0; 68 | } 69 | 70 | int setGate(float gate) { 71 | if(arp_state.gate != gate) { 72 | arp_state.gate = gate; 73 | return -1; 74 | } 75 | return 0; 76 | } 77 | 78 | int setCycle(int cycle) { 79 | if(arp_state.cycle != cycle) { 80 | arp_state.cycle = cycle; 81 | return -1; 82 | } 83 | return 0; 84 | } 85 | 86 | int setSkip(float skip) { 87 | if(arp_state.skip != skip) { 88 | arp_state.skip = skip; 89 | return -1; 90 | } 91 | return 0; 92 | } 93 | 94 | int setDir(enum dirtype dir) { 95 | if(arp_state.dir != dir) { 96 | arp_state.dir = dir; 97 | return -1; 98 | } 99 | return 0; 100 | } 101 | 102 | void updateArpeggioNotes() { 103 | int i; 104 | switch(arp_state.chord) { 105 | case OCTAVE: 106 | for(i = 0; i < arp_state.range; i++) { 107 | arp_state.arpeggio_notes[i] = 12 * i; 108 | } 109 | //lv2_log_error(&self.logger, "%d %d %d\n", i, self.arpeggio_notes[0], self.arpeggio_notes[1]); 110 | arp_state.arpeggio_length = i; 111 | break; 112 | case MAJOR: 113 | for(i = 0; i < arp_state.range; i++) { 114 | arp_state.arpeggio_notes[3 * i + 0] = 12 * i; 115 | arp_state.arpeggio_notes[3 * i + 1] = 12 * i + 4; 116 | arp_state.arpeggio_notes[3 * i + 2] = 12 * i + 3; 117 | } 118 | arp_state.arpeggio_length = 3 * i; 119 | break; 120 | case MINOR: 121 | for(i = 0; i < arp_state.range; i++) { 122 | arp_state.arpeggio_notes[3 * i + 0] = 12 * i; 123 | arp_state.arpeggio_notes[3 * i + 1] = 12 * i + 3; 124 | arp_state.arpeggio_notes[3 * i + 2] = 12 * i + 4; 125 | } 126 | arp_state.arpeggio_length = 3 * i; 127 | break; 128 | } 129 | 130 | if(arp_state.dir == DIR_DOWN) { 131 | // reverse the order 132 | for(i = 0; i < arp_state.arpeggio_length/2; i++) { 133 | uint8_t swap = arp_state.arpeggio_notes[i]; 134 | arp_state.arpeggio_notes[i] = 135 | arp_state.arpeggio_notes[arp_state.arpeggio_length - i]; 136 | arp_state.arpeggio_notes[arp_state.arpeggio_length - i] = swap; 137 | } 138 | 139 | } 140 | 141 | if(arp_state.dir == DIR_UPDOWN) { 142 | for(i = 0; i < arp_state.arpeggio_length; i++) { 143 | arp_state.arpeggio_notes[2 * arp_state.arpeggio_length - i] = 144 | arp_state.arpeggio_notes[i]; 145 | } 146 | arp_state.arpeggio_length = 2 * arp_state.arpeggio_length; 147 | } 148 | resetArpeggio(); 149 | } 150 | 151 | void resetArpeggio() { 152 | srandom(time(NULL)); 153 | arp_state.note_index = 0; 154 | } 155 | 156 | uint8_t nextNote(uint8_t base_note) { 157 | uint8_t note = base_note + arp_state.arpeggio_notes[ 158 | arp_state.note_index % arp_state.arpeggio_length]; 159 | 160 | if(arp_state.cycle > 0) { 161 | if((arp_state.note_index % arp_state.arpeggio_length) == 162 | (arp_state.cycle % arp_state.arpeggio_length)) { 163 | ++arp_state.note_index; 164 | } 165 | } 166 | 167 | if((random() % 100) < arp_state.skip) { 168 | note = 128; 169 | } 170 | 171 | ++arp_state.note_index; 172 | return note; 173 | } 174 | 175 | float note_as_fraction_of_bar(int beat_unit, int beats_per_bar) { 176 | // return the arpeggiator step as a fraction of a bar 177 | float note_length[] = { 1, 2, 4, 8, 16, 32 }; 178 | return beats_per_bar / (note_length[arp_state.time] * beat_unit); 179 | } 180 | 181 | -------------------------------------------------------------------------------- /simplearpeggiator_gui_qt5.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "lv2/lv2plug.in/ns/lv2core/lv2.h" 10 | #include "lv2/lv2plug.in/ns/extensions/ui/ui.h" 11 | 12 | #include "simplearpeggiator.h" 13 | 14 | class SimpleArpeggiatorGUI : public QWidget { 15 | Q_OBJECT 16 | 17 | public: 18 | SimpleArpeggiatorGUI(QWidget* parent = 0); 19 | ~SimpleArpeggiatorGUI(); 20 | QHBoxLayout* layout; 21 | QVBoxLayout* v1_layout; 22 | QVBoxLayout* v2_layout; 23 | QVBoxLayout* v3_layout; 24 | 25 | QLabel* chord_label; 26 | QRadioButton* chord_octave; 27 | QRadioButton* chord_major; 28 | QRadioButton* chord_minor; 29 | QGroupBox* chord_group; 30 | QVBoxLayout* chord_layout; 31 | QSpacerItem *chord_spacer; 32 | 33 | QLabel* dir_label; 34 | QRadioButton* dir_up; 35 | QRadioButton* dir_down; 36 | QRadioButton* dir_updown; 37 | QGroupBox* dir_group; 38 | QVBoxLayout* dir_layout; 39 | QSpacerItem *dir_spacer; 40 | 41 | QLabel* time_label; 42 | QRadioButton* time_1_1; 43 | QRadioButton* time_1_2; 44 | QRadioButton* time_1_4; 45 | QRadioButton* time_1_8; 46 | QRadioButton* time_1_16; 47 | QRadioButton* time_1_32; 48 | QGroupBox* time_group; 49 | QVBoxLayout* time_layout; 50 | QSpacerItem *time_spacer; 51 | 52 | QDial* range_dial; 53 | QLabel* range_label; 54 | QGroupBox* range_group; 55 | QVBoxLayout* range_layout; 56 | QSpacerItem *range_spacer; 57 | 58 | QDial* gate_dial; 59 | QLabel* gate_label; 60 | QGroupBox* gate_group; 61 | QVBoxLayout* gate_layout; 62 | QSpacerItem *gate_spacer; 63 | 64 | QDial* cycle_dial; 65 | QLabel* cycle_label; 66 | QGroupBox* cycle_group; 67 | QVBoxLayout* cycle_layout; 68 | QSpacerItem *cycle_spacer; 69 | 70 | QDial* skip_dial; 71 | QLabel* skip_label; 72 | QGroupBox* skip_group; 73 | QVBoxLayout* skip_layout; 74 | QSpacerItem *skip_spacer; 75 | 76 | float gate; 77 | 78 | LV2UI_Controller controller; 79 | LV2UI_Write_Function write_function; 80 | 81 | public slots: 82 | void chordChanged(bool checked); 83 | void timeChanged(bool checked); 84 | void rangeChanged(int value); 85 | void gateChanged(int value); 86 | void cycleChanged(int value); 87 | void skipChanged(int value); 88 | void dirChanged(bool checked); 89 | 90 | }; 91 | 92 | SimpleArpeggiatorGUI::SimpleArpeggiatorGUI(QWidget* parent) 93 | : QWidget(parent) { 94 | QRadioButton(); 95 | 96 | 97 | chord_group = new QGroupBox(); 98 | chord_label = new QLabel("Chord"); 99 | chord_octave = new QRadioButton("Octave"); 100 | chord_major = new QRadioButton("Major"); 101 | chord_minor = new QRadioButton("Minor"); 102 | chord_spacer = new QSpacerItem(20,40,QSizePolicy::Minimum, QSizePolicy::Expanding); 103 | chord_layout = new QVBoxLayout(); 104 | chord_layout->addWidget(chord_label); 105 | chord_layout->addWidget(chord_octave); 106 | chord_layout->addWidget(chord_major); 107 | chord_layout->addWidget(chord_minor); 108 | chord_layout->addItem(chord_spacer); 109 | chord_group->setLayout(chord_layout); 110 | 111 | dir_group = new QGroupBox(); 112 | dir_label = new QLabel("direction"); 113 | dir_up = new QRadioButton("up"); 114 | dir_down = new QRadioButton("down"); 115 | dir_updown = new QRadioButton("up-down"); 116 | dir_spacer = new QSpacerItem(20,40,QSizePolicy::Minimum, QSizePolicy::Expanding); 117 | dir_layout = new QVBoxLayout(); 118 | dir_layout->addWidget(dir_label); 119 | dir_layout->addWidget(dir_up); 120 | dir_layout->addWidget(dir_down); 121 | dir_layout->addWidget(dir_updown); 122 | dir_layout->addItem(dir_spacer); 123 | dir_group->setLayout(dir_layout); 124 | 125 | time_group = new QGroupBox(); 126 | time_label = new QLabel("Time"); 127 | time_1_1 = new QRadioButton("whole"); 128 | time_1_2 = new QRadioButton("1/2"); 129 | time_1_4 = new QRadioButton("1/4"); 130 | time_1_8 = new QRadioButton("1/8"); 131 | time_1_16 = new QRadioButton("1/16"); 132 | time_1_32 = new QRadioButton("1/32"); 133 | time_spacer = new QSpacerItem(20,40,QSizePolicy::Minimum, QSizePolicy::Expanding); 134 | time_layout = new QVBoxLayout(); 135 | time_layout->addWidget(time_label); 136 | time_layout->addWidget(time_1_1); 137 | time_layout->addWidget(time_1_2); 138 | time_layout->addWidget(time_1_4); 139 | time_layout->addWidget(time_1_8); 140 | time_layout->addWidget(time_1_16); 141 | time_layout->addWidget(time_1_32); 142 | time_layout->addItem(time_spacer); 143 | time_group->setLayout(time_layout); 144 | 145 | range_group = new QGroupBox(); 146 | range_label = new QLabel("Range"); 147 | range_dial = new QDial(); 148 | range_dial->setRange(1, 9); 149 | range_dial->setValue(9); 150 | range_dial->setNotchesVisible(true); 151 | range_spacer = new QSpacerItem(20,40,QSizePolicy::Minimum, QSizePolicy::Expanding); 152 | range_layout = new QVBoxLayout(); 153 | range_layout->addWidget(range_label); 154 | range_layout->addWidget(range_dial); 155 | range_layout->addItem(range_spacer); 156 | range_group->setLayout(range_layout); 157 | 158 | gate_group = new QGroupBox(); 159 | gate_label = new QLabel("Gate"); 160 | gate_dial = new QDial(); 161 | gate_dial->setRange(0, 100); 162 | gate_dial->setNotchesVisible(true); 163 | gate_spacer = new QSpacerItem(20,40,QSizePolicy::Minimum, QSizePolicy::Expanding); 164 | gate_layout = new QVBoxLayout(); 165 | gate_layout->addWidget(gate_label); 166 | gate_layout->addWidget(gate_dial); 167 | gate_layout->addItem(gate_spacer); 168 | gate_group->setLayout(gate_layout); 169 | 170 | skip_group = new QGroupBox(); 171 | skip_label = new QLabel("skip"); 172 | skip_dial = new QDial(); 173 | skip_dial->setRange(0, 100); 174 | skip_dial->setNotchesVisible(true); 175 | skip_spacer = new QSpacerItem(20,40,QSizePolicy::Minimum, QSizePolicy::Expanding); 176 | skip_layout = new QVBoxLayout(); 177 | skip_layout->addWidget(skip_label); 178 | skip_layout->addWidget(skip_dial); 179 | skip_layout->addItem(skip_spacer); 180 | skip_group->setLayout(skip_layout); 181 | 182 | cycle_group = new QGroupBox(); 183 | cycle_label = new QLabel("cycle"); 184 | cycle_dial = new QDial(); 185 | cycle_dial->setRange(0, 6); 186 | cycle_dial->setNotchesVisible(true); 187 | cycle_spacer = new QSpacerItem(20,40,QSizePolicy::Minimum, QSizePolicy::Expanding); 188 | cycle_layout = new QVBoxLayout(); 189 | cycle_layout->addWidget(cycle_label); 190 | cycle_layout->addWidget(cycle_dial); 191 | cycle_layout->addItem(cycle_spacer); 192 | cycle_group->setLayout(cycle_layout); 193 | 194 | layout = new QHBoxLayout(); 195 | v1_layout = new QVBoxLayout(); 196 | v2_layout = new QVBoxLayout(); 197 | v3_layout = new QVBoxLayout(); 198 | v1_layout->addWidget(chord_group); 199 | v1_layout->addWidget(dir_group); 200 | v2_layout->addWidget(range_group); 201 | v2_layout->addWidget(cycle_group); 202 | v3_layout->addWidget(gate_group); 203 | v3_layout->addWidget(skip_group); 204 | layout->addLayout(v1_layout); 205 | layout->addWidget(time_group); 206 | layout->addLayout(v2_layout); 207 | layout->addLayout(v3_layout); 208 | setLayout(layout); 209 | 210 | #ifndef QT_NO_TOOLTIP 211 | chord_group->setToolTip("The chord defines what notes are played in each octave"); 212 | dir_group->setToolTip("How the arpeggio is played"); 213 | time_group->setToolTip("The length of each arpeggio note"); 214 | range_group->setToolTip("The arpeggio range in octaves"); 215 | gate_group->setToolTip("The percentiage of a whole apreggio note that should be played. Seting it to less than 100% can create cool staccato effects."); 216 | cycle_group->setToolTip("Cycle will jump over the 1-6th step in the arpeggio"); 217 | skip_group->setToolTip("Skip will cause the arpeggio to pause randomly if set to more than 0%"); 218 | #endif 219 | 220 | chord_group->setStyleSheet("QGroupBox { border: 1px solid gray;}"); 221 | dir_group->setStyleSheet("QGroupBox { border: 1px solid gray;}"); 222 | time_group->setStyleSheet("QGroupBox { border: 1px solid gray;}"); 223 | range_group->setStyleSheet("QGroupBox { border: 1px solid gray;}"); 224 | gate_group->setStyleSheet("QGroupBox { border: 1px solid gray;}"); 225 | cycle_group->setStyleSheet("QGroupBox { border: 1px solid gray;}"); 226 | skip_group->setStyleSheet("QGroupBox { border: 1px solid gray;}"); 227 | } 228 | 229 | SimpleArpeggiatorGUI::~SimpleArpeggiatorGUI() { 230 | // trying to delete the allocation in the constructor gives 231 | // segementation fault 232 | } 233 | 234 | void SimpleArpeggiatorGUI::chordChanged(bool checked) { 235 | float chord = 0; 236 | if(!checked) return; 237 | if(chord_octave->isChecked()) chord = 0; 238 | if(chord_major->isChecked()) chord = 1; 239 | if(chord_minor->isChecked()) chord = 2; 240 | write_function(controller, SIMPLEARPEGGIATOR_CHORD, sizeof(gate), 0, &chord); 241 | } 242 | 243 | void SimpleArpeggiatorGUI::timeChanged(bool checked) { 244 | float time = 0; 245 | if(!checked) return; 246 | if(time_1_1->isChecked()) time = 0; 247 | if(time_1_2->isChecked()) time = 1; 248 | if(time_1_4->isChecked()) time = 2; 249 | if(time_1_8->isChecked()) time = 3; 250 | if(time_1_16->isChecked()) time = 4; 251 | if(time_1_32->isChecked()) time = 5; 252 | write_function(controller, SIMPLEARPEGGIATOR_TIME, sizeof(gate), 0, &time); 253 | } 254 | 255 | void SimpleArpeggiatorGUI::rangeChanged(int value) { 256 | float range = range_dial->value(); 257 | range_label->setText(QString("Range: %1").arg(range)); 258 | write_function(controller, SIMPLEARPEGGIATOR_RANGE, sizeof(gate), 0, &range); 259 | } 260 | 261 | void SimpleArpeggiatorGUI::gateChanged(int value) { 262 | float gate = gate_dial->value(); 263 | gate_label->setText(QString("Gate: %1 %").arg(gate)); 264 | write_function(controller, SIMPLEARPEGGIATOR_GATE, sizeof(gate), 0, &gate); 265 | } 266 | 267 | void SimpleArpeggiatorGUI::cycleChanged(int value) { 268 | float cycle = cycle_dial->value(); 269 | cycle_label->setText(QString("Cycle: %1").arg(cycle)); 270 | write_function(controller, SIMPLEARPEGGIATOR_CYCLE, sizeof(cycle), 0, &cycle); 271 | } 272 | 273 | void SimpleArpeggiatorGUI::skipChanged(int value) { 274 | float skip = skip_dial->value(); 275 | skip_label->setText(QString("Skip: %1 %").arg(skip)); 276 | write_function(controller, SIMPLEARPEGGIATOR_SKIP, sizeof(skip), 0, &skip); 277 | } 278 | 279 | void SimpleArpeggiatorGUI::dirChanged(bool checked) { 280 | float dir = 0; 281 | if(!checked) return; 282 | if(dir_up->isChecked()) dir = 0; 283 | if(dir_down->isChecked()) dir = 1; 284 | if(dir_updown->isChecked()) dir = 2; 285 | write_function(controller, SIMPLEARPEGGIATOR_DIR, sizeof(gate), 0, &dir); 286 | } 287 | 288 | LV2UI_Handle instantiate(const struct _LV2UI_Descriptor* descriptor, 289 | const char* plugin_uri, const char* bundle_path, 290 | LV2UI_Write_Function write_function, 291 | LV2UI_Controller controller, LV2UI_Widget* widget, 292 | const LV2_Feature* const* features) { 293 | 294 | if (strcmp(plugin_uri, SIMPLEARPEGGIATOR_URI) != 0) { 295 | fprintf(stderr, "AMP_UI error: this GUI does not support plugin with URI %s\n", plugin_uri); 296 | return NULL; 297 | } 298 | 299 | SimpleArpeggiatorGUI* pluginGui = new SimpleArpeggiatorGUI(); 300 | *widget = pluginGui; 301 | 302 | if (pluginGui == NULL) return NULL; 303 | 304 | pluginGui->controller = controller; 305 | pluginGui->write_function = write_function; 306 | 307 | QObject::connect(pluginGui->chord_octave, SIGNAL(toggled(bool)), 308 | pluginGui, SLOT(chordChanged(bool))); 309 | QObject::connect(pluginGui->chord_major, SIGNAL(toggled(bool)), 310 | pluginGui, SLOT(chordChanged(bool))); 311 | QObject::connect(pluginGui->chord_minor, SIGNAL(toggled(bool)), 312 | pluginGui, SLOT(chordChanged(bool))); 313 | QObject::connect(pluginGui->time_1_1, SIGNAL(toggled(bool)), 314 | pluginGui, SLOT(timeChanged(bool))); 315 | QObject::connect(pluginGui->time_1_2, SIGNAL(toggled(bool)), 316 | pluginGui, SLOT(timeChanged(bool))); 317 | QObject::connect(pluginGui->time_1_4, SIGNAL(toggled(bool)), 318 | pluginGui, SLOT(timeChanged(bool))); 319 | QObject::connect(pluginGui->time_1_8, SIGNAL(toggled(bool)), 320 | pluginGui, SLOT(timeChanged(bool))); 321 | QObject::connect(pluginGui->time_1_16, SIGNAL(toggled(bool)), 322 | pluginGui, SLOT(timeChanged(bool))); 323 | QObject::connect(pluginGui->time_1_32, SIGNAL(toggled(bool)), 324 | pluginGui, SLOT(timeChanged(bool))); 325 | QObject::connect(pluginGui->range_dial, SIGNAL(valueChanged(int)), 326 | pluginGui, SLOT(rangeChanged(int))); 327 | QObject::connect(pluginGui->gate_dial, SIGNAL(valueChanged(int)), 328 | pluginGui, SLOT(gateChanged(int))); 329 | QObject::connect(pluginGui->cycle_dial, SIGNAL(valueChanged(int)), 330 | pluginGui, SLOT(cycleChanged(int))); 331 | QObject::connect(pluginGui->skip_dial, SIGNAL(valueChanged(int)), 332 | pluginGui, SLOT(skipChanged(int))); 333 | QObject::connect(pluginGui->dir_up, SIGNAL(toggled(bool)), 334 | pluginGui, SLOT(dirChanged(bool))); 335 | QObject::connect(pluginGui->dir_down, SIGNAL(toggled(bool)), 336 | pluginGui, SLOT(dirChanged(bool))); 337 | QObject::connect(pluginGui->dir_updown, SIGNAL(toggled(bool)), 338 | pluginGui, SLOT(dirChanged(bool))); 339 | 340 | return (LV2UI_Handle)pluginGui; 341 | } 342 | 343 | void cleanup(LV2UI_Handle ui) { 344 | SimpleArpeggiatorGUI* pluginGui = (SimpleArpeggiatorGUI*) ui; 345 | 346 | delete pluginGui; 347 | } 348 | 349 | void port_event(LV2UI_Handle ui, uint32_t port_index, uint32_t buffer_size, 350 | uint32_t format, const void* buffer) { 351 | SimpleArpeggiatorGUI* pluginGui = (SimpleArpeggiatorGUI*) ui; 352 | float* pval = (float*) buffer; 353 | int n; 354 | 355 | if ((format != 0) || (port_index < 0) || (port_index >= SIMPLEARPEGGIATOR_N_PORTS)) { 356 | return; 357 | } 358 | 359 | // Addition by 0.5 is to round to int correctly 360 | switch(port_index) { 361 | case SIMPLEARPEGGIATOR_CHORD: 362 | n = (int) (*pval + 0.5); 363 | if(n == 0) pluginGui->chord_octave->setChecked(true); 364 | if(n == 1) pluginGui->chord_major->setChecked(true); 365 | if(n == 2) pluginGui->chord_minor->setChecked(true); 366 | break; 367 | case SIMPLEARPEGGIATOR_TIME: 368 | n = (int) (*pval + 0.5); 369 | if(n == 0) pluginGui->time_1_1->setChecked(true); 370 | if(n == 1) pluginGui->time_1_2->setChecked(true); 371 | if(n == 2) pluginGui->time_1_4->setChecked(true); 372 | if(n == 3) pluginGui->time_1_8->setChecked(true); 373 | if(n == 4) pluginGui->time_1_16->setChecked(true); 374 | if(n == 5) pluginGui->time_1_32->setChecked(true); 375 | break; 376 | case SIMPLEARPEGGIATOR_RANGE: 377 | pluginGui->range_dial->setValue((int)(*pval + 0.5)); 378 | break; 379 | case SIMPLEARPEGGIATOR_GATE: 380 | pluginGui->gate_dial->setValue((int)(*pval + 0.5)); 381 | break; 382 | case SIMPLEARPEGGIATOR_CYCLE: 383 | pluginGui->cycle_dial->setValue((int)(*pval + 0.5)); 384 | break; 385 | case SIMPLEARPEGGIATOR_SKIP: 386 | pluginGui->skip_dial->setValue((int)(*pval + 0.5)); 387 | break; 388 | case SIMPLEARPEGGIATOR_DIR: 389 | n = (int) (*pval + 0.5); 390 | if(n == 0) pluginGui->dir_up->setChecked(true); 391 | if(n == 1) pluginGui->dir_down->setChecked(true); 392 | if(n == 2) pluginGui->dir_updown->setChecked(true); 393 | break; 394 | } 395 | } 396 | 397 | const void* extension_data(const char* uri) { return NULL; } 398 | 399 | static LV2UI_Descriptor descriptor = { 400 | SIMPLEARPEGGIATOR_URI "#qt5", instantiate, cleanup, port_event, extension_data 401 | }; 402 | 403 | const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) { 404 | switch (index) { 405 | case 0: return &descriptor; 406 | default: return NULL; 407 | } 408 | } 409 | 410 | #include "simplearpeggiator_gui_qt5.moc.cpp" 411 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /simplearpeggiator.c: -------------------------------------------------------------------------------- 1 | /* 2 | SimpleArpeggiator LV2 Plugin 3 | Copyright 2017 Johan Berntsson 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #ifndef __cplusplus 22 | #include 23 | #endif 24 | #include 25 | 26 | #include "lv2/lv2plug.in/ns/lv2core/lv2.h" 27 | #include "lv2/lv2plug.in/ns/ext/atom/atom.h" 28 | #include "lv2/lv2plug.in/ns/ext/atom/util.h" 29 | #include "lv2/lv2plug.in/ns/ext/time/time.h" 30 | #include "lv2/lv2plug.in/ns/ext/midi/midi.h" 31 | #include "lv2/lv2plug.in/ns/ext/log/logger.h" 32 | #include "lv2/lv2plug.in/ns/ext/state/state.h" 33 | 34 | #include "arpeggiator.h" 35 | #include "simplearpeggiator.h" 36 | 37 | typedef struct { 38 | // Data types for communication with host 39 | LV2_URID atom_Blank; 40 | LV2_URID atom_Int; 41 | LV2_URID atom_Float; 42 | LV2_URID atom_Object; 43 | LV2_URID atom_Path; 44 | LV2_URID atom_Resource; 45 | LV2_URID atom_Sequence; 46 | LV2_URID atom_URID; 47 | LV2_URID atom_eventTransfer; 48 | // Midi parameters 49 | LV2_URID midi_Event; 50 | // Time parameters 51 | LV2_URID time_Position; 52 | LV2_URID time_beatsPerBar; // top number in a time signature, usually 4 (for 4/4) 53 | LV2_URID time_beatUnit; // bottom number in a time signature, usually 4 (for 4/4) 54 | LV2_URID time_barBeat; // The beat number within the bar, from 0 to beatsPerBar 55 | LV2_URID time_beatsPerMinute; // Tempo in beats per minute. 56 | LV2_URID time_speed; // fraction of normal speed. 0.0 is stopped, 1.0 is normal speed 57 | } SimpleArpeggiatorURIs; 58 | 59 | typedef struct { 60 | LV2_Atom_Event event; 61 | uint8_t msg[3]; 62 | } MIDINoteEvent; 63 | 64 | typedef struct { 65 | // Features 66 | LV2_URID_Map* map; 67 | LV2_Log_Log* log; 68 | 69 | // Ports 70 | const LV2_Atom_Sequence* in_port; 71 | LV2_Atom_Sequence* out_port; 72 | float* chord_ptr; 73 | float* range_ptr; /* 1 - 9 octaves */ 74 | float* time_ptr; 75 | float* gate_ptr; /* 0 - 100 % */ 76 | float* cycle_ptr; /* 0 - 6 notes to skip */ 77 | float* skip_ptr; /* 0 - 100 % */ 78 | float* dir_ptr; 79 | 80 | // Variables to keep track of the tempo information sent by the host 81 | double rate; // Sample rate 82 | float bpm; // Beats per minute (tempo) 83 | float speed; // Transport speed (usually 0=stop, 1=play) 84 | uint32_t beat_unit; // bottom number in a time signature 85 | uint32_t beats_per_bar; // top number in a time signature 86 | uint32_t frames_per_beat; // number of frames in one beat 87 | uint32_t elapsed_frames; // Frames since the start of the last click 88 | 89 | // arpeggio info 90 | uint8_t base_note; // base note of the current arpeggio 91 | MIDINoteEvent arpeggiator_note; // the currently played apreggio note 92 | uint32_t arpeggiator_note_last_frame; // scheduled note off (frames) 93 | 94 | // Logger convenience API 95 | LV2_Log_Logger logger; 96 | 97 | // URIs 98 | SimpleArpeggiatorURIs uris; 99 | } SimpleArpeggiator; 100 | 101 | static void connect_port( 102 | LV2_Handle instance, 103 | uint32_t port, 104 | void* data) { 105 | FILE *f; 106 | SimpleArpeggiator* self = (SimpleArpeggiator*)instance; 107 | switch (port) { 108 | case SIMPLEARPEGGIATOR_IN: 109 | self->in_port = (const LV2_Atom_Sequence*)data; 110 | break; 111 | case SIMPLEARPEGGIATOR_OUT: 112 | self->out_port = (LV2_Atom_Sequence*)data; 113 | break; 114 | case SIMPLEARPEGGIATOR_CHORD: 115 | self->chord_ptr = (float *)data; 116 | break; 117 | case SIMPLEARPEGGIATOR_RANGE: 118 | self->range_ptr = (float *)data; 119 | break; 120 | case SIMPLEARPEGGIATOR_TIME: 121 | self->time_ptr = (float *) data; 122 | break; 123 | case SIMPLEARPEGGIATOR_GATE: 124 | self->gate_ptr = (float*)data; 125 | break; 126 | case SIMPLEARPEGGIATOR_CYCLE: 127 | self->cycle_ptr = (float*)data; 128 | break; 129 | case SIMPLEARPEGGIATOR_SKIP: 130 | self->skip_ptr = (float*)data; 131 | break; 132 | case SIMPLEARPEGGIATOR_DIR: 133 | self->dir_ptr = (float*)data; 134 | break; 135 | default: 136 | break; 137 | } 138 | } 139 | 140 | static void updateParameters(SimpleArpeggiator* self) { 141 | bool updateArpeggiato = false; 142 | 143 | if(setChord((enum chordtype) *self->chord_ptr)) updateArpeggiato = true; 144 | if(setRange((int) *self->range_ptr)) updateArpeggiato = true; 145 | if(setTime((enum timetype) *self->time_ptr)) updateArpeggiato = true; 146 | if(setGate( *self->gate_ptr)) updateArpeggiato = true; 147 | if(setCycle((int) *self->cycle_ptr)) updateArpeggiato = true; 148 | if(setSkip( *self->skip_ptr)) updateArpeggiato = true; 149 | if(setDir((enum dirtype) *self->dir_ptr)) updateArpeggiato = true; 150 | 151 | if(updateArpeggiato) { 152 | lv2_log_error(&self->logger, "updating arpeggio\n"); 153 | updateArpeggioNotes(); 154 | } 155 | } 156 | 157 | // The activate() method resets the state completely 158 | static void activate(LV2_Handle instance) { 159 | SimpleArpeggiator* self = (SimpleArpeggiator*)instance; 160 | //fprintf(stderr, "activate\n"); 161 | self->elapsed_frames = 0; 162 | self->base_note = 128; 163 | 164 | updateParameters(self); 165 | } 166 | 167 | static LV2_Handle instantiate( 168 | const LV2_Descriptor* descriptor, 169 | double rate, 170 | const char* path, 171 | const LV2_Feature* const* features) { 172 | // Allocate and initialise instance structure. 173 | SimpleArpeggiator* self = (SimpleArpeggiator*)malloc(sizeof(SimpleArpeggiator)); 174 | if (!self) { 175 | return NULL; 176 | } 177 | memset(self, 0, sizeof(SimpleArpeggiator)); 178 | 179 | // Get host features 180 | for (int i = 0; features[i]; ++i) { 181 | if (!strcmp(features[i]->URI, LV2_URID__map)) { 182 | self->map = (LV2_URID_Map*)features[i]->data; 183 | } else if (!strcmp(features[i]->URI, LV2_LOG__log)) { 184 | self->log = (LV2_Log_Log*)features[i]->data; 185 | } 186 | } 187 | if (!self->map) { 188 | fprintf(stderr, "Missing feature urid:map\n"); 189 | free(self); 190 | return NULL; 191 | } 192 | 193 | // Map URIs 194 | SimpleArpeggiatorURIs* const uris = &self->uris; 195 | LV2_URID_Map* const map = self->map; 196 | uris->atom_Blank = map->map(map->handle, LV2_ATOM__Blank); 197 | uris->atom_Int = map->map(map->handle, LV2_ATOM__Int); 198 | uris->atom_Float = map->map(map->handle, LV2_ATOM__Float); 199 | uris->atom_Object = map->map(map->handle, LV2_ATOM__Object); 200 | uris->atom_Path = map->map(map->handle, LV2_ATOM__Path); 201 | uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource); 202 | uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence); 203 | uris->atom_URID = map->map(map->handle, LV2_ATOM__URID); 204 | uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer); 205 | uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent); 206 | uris->time_Position = map->map(map->handle, LV2_TIME__Position); 207 | uris->time_beatsPerBar = map->map(map->handle, LV2_TIME__beatsPerBar); 208 | uris->time_beatUnit = map->map(map->handle, LV2_TIME__beatUnit); 209 | uris->time_barBeat = map->map(map->handle, LV2_TIME__barBeat); 210 | uris->time_beatsPerMinute= map->map(map->handle, LV2_TIME__beatsPerMinute); 211 | uris->time_speed = map->map(map->handle, LV2_TIME__speed); 212 | 213 | // initialise forge/logger 214 | lv2_log_logger_init(&self->logger, self->map, self->log); 215 | 216 | // Initialise instance fields 217 | self->rate = rate; 218 | self->bpm = 120.0f; // default (will be updated later) 219 | self->frames_per_beat = 60.0f / self->bpm * self->rate; 220 | 221 | // setting parameter defaults to trigger updates in activate later() 222 | setChord(CHORD_ERROR); 223 | setTime(NOTE_ERROR); 224 | setDir(DIR_ERROR); 225 | 226 | return (LV2_Handle)self; 227 | } 228 | 229 | static void cleanup(LV2_Handle instance) { 230 | free(instance); 231 | } 232 | 233 | 234 | static void update_time( 235 | SimpleArpeggiator* self, 236 | const LV2_Atom_Object* obj, 237 | const LV2_Atom_Event* ev) { 238 | const SimpleArpeggiatorURIs* uris = &self->uris; 239 | 240 | // Received new transport position/speed 241 | LV2_Atom *beat = NULL, *bpm = NULL, *speed = NULL; 242 | LV2_Atom *beatsperbar = NULL, *beatunit = NULL; 243 | lv2_atom_object_get(obj, 244 | uris->time_barBeat, &beat, 245 | uris->time_beatsPerMinute, &bpm, 246 | uris->time_speed, &speed, 247 | uris->time_beatsPerBar, &beatsperbar, 248 | uris->time_beatUnit, &beatunit, 249 | NULL); 250 | if (bpm && bpm->type == uris->atom_Float) { 251 | if(self->bpm != ((LV2_Atom_Float*) bpm)->body) { 252 | // Tempo changed, update BPM 253 | self->bpm = ((LV2_Atom_Float*) bpm)->body; 254 | self->frames_per_beat = 60.0f / self->bpm * self->rate; 255 | //lv2_log_error(&self->logger, "bpm %f\n", self->bpm); 256 | } 257 | } 258 | if (speed && speed->type == uris->atom_Float) { 259 | if(self->speed != ((LV2_Atom_Float*) speed)->body) { 260 | // Speed changed, e.g. 0 (stop) to 1 (play) 261 | self->speed = ((LV2_Atom_Float*) speed)->body; 262 | //lv2_log_error(&self->logger, "speed %f\n", self->speed); 263 | if(self->speed > 0) { 264 | // restarted 265 | resetArpeggio(); 266 | } 267 | } 268 | } 269 | if (beatsperbar && beatsperbar->type == uris->atom_Float) { 270 | if(self->beats_per_bar != (int32_t) ((LV2_Atom_Float*) beatsperbar)->body) { 271 | // Number of beats in a bar changed 272 | self->beats_per_bar = (int32_t) ((LV2_Atom_Float*) beatsperbar)->body; 273 | //lv2_log_error(&self->logger, "beats_per_bar %d\n", self->beats_per_bar); 274 | } 275 | } 276 | if (beatunit && beatunit->type == uris->atom_Int) { 277 | if(self->beat_unit != (int32_t) ((LV2_Atom_Int*) beatunit)->body) { 278 | // Number of beats in a bar changed 279 | self->beat_unit = (int32_t) ((LV2_Atom_Int*) beatunit)->body; 280 | //lv2_log_error(&self->logger, "beat_unit %d\n", self->beat_unit); 281 | } 282 | } 283 | if (beat && beat->type == uris->atom_Float) { 284 | // Received a beat position, synchronise 285 | const float bar_beats = ((LV2_Atom_Float*)beat)->body; // eg. 2.031 286 | const float beat_beats = bar_beats - floorf(bar_beats); // 0.031 287 | if(bar_beats < 1) { 288 | // new bar 289 | updateParameters(self); 290 | //self->elapsed_frames = beat_beats * self->frames_per_beat; // already processed frames 291 | //lv2_log_error(&self->logger, "beat %f %d/%d %d\n", beat_beats, self->beats_per_bar, self->beat_unit, self->elapsed_frames); 292 | } 293 | } 294 | 295 | } 296 | 297 | static void update_arp( 298 | SimpleArpeggiator* self, 299 | uint32_t begin, 300 | uint32_t end, 301 | const uint32_t out_capacity) { 302 | if(self->speed < 1.0) return; 303 | 304 | float step_ratio = note_as_fraction_of_bar(self->beat_unit, self->beats_per_bar); 305 | uint32_t step_in_frames = (self->frames_per_beat * self->beats_per_bar) * step_ratio; 306 | 307 | for (uint32_t i = begin; i < end; ++i) { 308 | if(self->elapsed_frames == self->arpeggiator_note_last_frame) { 309 | self->arpeggiator_note.msg[0] = 0x80; 310 | lv2_atom_sequence_append_event( 311 | self->out_port, out_capacity, &self->arpeggiator_note.event); 312 | } 313 | if(self->elapsed_frames % step_in_frames == 0) { 314 | if(self->base_note < 128) { 315 | self->arpeggiator_note.event.time.frames = 0; 316 | self->arpeggiator_note.event.body.type = self->uris.midi_Event; 317 | self->arpeggiator_note.event.body.size = 3; 318 | self->arpeggiator_note.msg[0] = 0x90; 319 | self->arpeggiator_note.msg[1] = nextNote(self->base_note); 320 | self->arpeggiator_note.msg[2] = 127; 321 | 322 | if(self->arpeggiator_note.msg[1] < 128) { 323 | // calculate note off time 324 | self->arpeggiator_note_last_frame = self->elapsed_frames + 325 | (getGate() * step_in_frames) / 100; 326 | // send the note to the midi bus 327 | lv2_atom_sequence_append_event( 328 | self->out_port, out_capacity, &self->arpeggiator_note.event); 329 | } 330 | } 331 | 332 | } 333 | ++self->elapsed_frames; 334 | } 335 | } 336 | 337 | static int update_midi( 338 | SimpleArpeggiator* self, 339 | const uint8_t* const msg, 340 | const uint32_t out_capacity 341 | ) { 342 | // return 0 if consumed by this filter 343 | //lv2_log_error(&self->logger, "midi command %x %d %d\n", msg[0], msg[1], msg[2]); 344 | 345 | switch (lv2_midi_message_type(msg)) { 346 | case LV2_MIDI_MSG_NOTE_ON: 347 | // only allow one note at a time 348 | if(self->base_note == 128) { 349 | //lv2_log_error(&self->logger, "note on %d\n", msg[1]); 350 | self->base_note = msg[1]; 351 | } 352 | return 0; 353 | case LV2_MIDI_MSG_NOTE_OFF: 354 | if(self->base_note == msg[1]) { 355 | //lv2_log_error(&self->logger, "note off %d\n", msg[1]); 356 | self->base_note = 128; 357 | } 358 | return 0; 359 | default: 360 | // Forward all other MIDI events directly 361 | return 1; 362 | } 363 | return 1; 364 | } 365 | 366 | static void run(LV2_Handle instance, uint32_t sample_count) { 367 | SimpleArpeggiator* self = (SimpleArpeggiator*)instance; 368 | SimpleArpeggiatorURIs* uris = &self->uris; 369 | 370 | // Initially self->out_port contains a Chunk with size set to capacity 371 | // Get the capacity 372 | const uint32_t out_capacity = self->out_port->atom.size; 373 | // Write an empty Sequence header to the output 374 | lv2_atom_sequence_clear(self->out_port); 375 | self->out_port->atom.type = self->in_port->atom.type; 376 | 377 | uint32_t last_t = 0; // range [0,sample_count] 378 | 379 | // Read incoming events 380 | LV2_ATOM_SEQUENCE_FOREACH(self->in_port, ev) { 381 | //lv2_log_error(&self->logger, "event %d\n", ev->body.type); 382 | if (ev->body.type == uris->atom_Object || 383 | ev->body.type == uris->atom_Blank) { 384 | const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; 385 | if (obj->body.otype == uris->time_Position) { 386 | // Received position information (bar/beat/bpm changes) 387 | update_time(self, obj, ev); 388 | } 389 | } else if (ev->body.type == uris->midi_Event) { 390 | const uint8_t* const msg = (const uint8_t*)(ev + 1); 391 | if(update_midi(self, msg, out_capacity)) { 392 | // check if midi note_on or note_off 393 | lv2_atom_sequence_append_event( 394 | self->out_port, out_capacity, ev); 395 | } 396 | } 397 | 398 | // update for this iteration 399 | update_arp(self, last_t, ev->time.frames, out_capacity); 400 | last_t = ev->time.frames; 401 | } 402 | 403 | // update for the remainder of the cycle 404 | update_arp(self, last_t, sample_count, out_capacity); 405 | } 406 | 407 | /* Not needed for basic preset save/restore. What is this? 408 | static LV2_State_Status state_save( 409 | LV2_Handle instance, 410 | LV2_State_Store_Function store, 411 | LV2_State_Handle handle, 412 | uint32_t flags, 413 | const LV2_Feature* const* features) { 414 | SimpleArpeggiator* self = (SimpleArpeggiator*) instance; 415 | if (!self) { 416 | return LV2_STATE_SUCCESS; 417 | } 418 | 419 | store(handle, self->uris.ui_spp, 420 | (void*)&self->ui_spp, sizeof(uint32_t), 421 | self->uris.atom_Int, 422 | LV2_STATE_IS_POD); 423 | 424 | store(handle, self->uris.ui_amp, 425 | (void*)&self->ui_amp, sizeof(float), 426 | self->uris.atom_Float, 427 | LV2_STATE_IS_POD); 428 | 429 | return LV2_STATE_SUCCESS; 430 | } 431 | 432 | static LV2_State_Status state_restore( 433 | LV2_Handle instance, 434 | LV2_State_Retrieve_Function retrieve, 435 | LV2_State_Handle handle, 436 | uint32_t flags, 437 | const LV2_Feature* const* features) { 438 | SimpleArpeggiator* self = (SimpleArpeggiator*) instance; 439 | 440 | 441 | size_t size; 442 | uint32_t type; 443 | uint32_t valflags; 444 | const void* spp = retrieve( 445 | handle, self->uris.ui_spp, &size, &type, &valflags); 446 | if (spp && size == sizeof(uint32_t) && type == self->uris.atom_Int) { 447 | self->ui_spp = *((const uint32_t*)spp); 448 | } 449 | 450 | const void* amp = retrieve( 451 | handle, self->uris.ui_amp, &size, &type, &valflags); 452 | if (amp && size == sizeof(float) && type == self->uris.atom_Float) { 453 | self->ui_amp = *((const float*)amp); 454 | } 455 | 456 | return LV2_STATE_SUCCESS; 457 | } 458 | */ 459 | 460 | static const void* extension_data(const char* uri) 461 | { 462 | /* Not needed for basic preset save/restore. What is this? 463 | static const LV2_State_Interface state = { state_save, state_restore }; 464 | if (!strcmp(uri, LV2_STATE__interface)) { 465 | return &state; 466 | } 467 | */ 468 | return NULL; 469 | } 470 | 471 | static const LV2_Descriptor descriptor = { 472 | SIMPLEARPEGGIATOR_URI, 473 | instantiate, 474 | connect_port, 475 | activate, 476 | run, 477 | NULL, // deactivate, 478 | cleanup, 479 | extension_data 480 | }; 481 | 482 | LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index) 483 | { 484 | switch (index) { 485 | case 0: 486 | return &descriptor; 487 | default: 488 | return NULL; 489 | } 490 | } 491 | 492 | --------------------------------------------------------------------------------