pax_global_header00006660000000000000000000000064140571153000014506gustar00rootroot0000000000000052 comment=38f4f68fda1b1bb26f50040ea5f18a4f8970cf91 BShapr-0.13/000077500000000000000000000000001405711530000126105ustar00rootroot00000000000000BShapr-0.13/.gitignore000066400000000000000000000000251405711530000145750ustar00rootroot00000000000000/BShapr.lv2/** .tags BShapr-0.13/BShapr-cv.ttl000066400000000000000000000537621405711530000151370ustar00rootroot00000000000000@prefix atom: . @prefix doap: . @prefix foaf: . @prefix rdf: . @prefix rdfs: . @prefix lv2: . @prefix time: . @prefix midi: . @prefix urid: . @prefix state: . @prefix ui: . @prefix rsz: . a foaf:Person; foaf:name "Sven Jaehnichen"; # foaf:mbox ; foaf:homepage . a ui:X11UI; ui:binary ; lv2:extensionData ui:idleInterface, ui:resize ; lv2:optionalFeature ui:resize ; lv2:requiredFeature ui:idleInterface . a lv2:Plugin, lv2:EnvelopePlugin, doap:Project ; doap:name "B.Shapr (CV)" ; rdfs:comment "Beat and LFO shaping plugin." ; doap:maintainer ; doap:license ; lv2:optionalFeature lv2:hardRTCapable ; lv2:extensionData state:interface ; lv2:binary ; lv2:requiredFeature urid:map ; ui:ui ; lv2:port [ a lv2:InputPort , atom:AtomPort ; atom:bufferType atom:Sequence ; atom:supports time:Position ; atom:supports midi:MidiEvent ; lv2:index 0 ; lv2:symbol "control" ; lv2:name "Control" ; ] , [ a lv2:OutputPort , atom:AtomPort ; atom:bufferType atom:Sequence ; lv2:index 1 ; lv2:symbol "notify" ; lv2:name "Notify" ; rsz:minimumSize 73000; ] , [ a lv2:AudioPort , lv2:InputPort ; lv2:index 2 ; lv2:symbol "lv2_audio_in_1" ; lv2:name "Audio Input 1" ; ] , [ a lv2:AudioPort , lv2:InputPort ; lv2:index 3 ; lv2:symbol "lv2_audio_in_2" ; lv2:name "Audio Input 2" ; ] , [ a lv2:AudioPort , lv2:OutputPort ; lv2:index 4 ; lv2:symbol "lv2_audio_out_1" ; lv2:name "Audio Output 1" ; ] , [ a lv2:AudioPort , lv2:OutputPort ; lv2:index 5 ; lv2:symbol "lv2_audio_out_2" ; lv2:name "Audio Output 2" ; ] , [ a lv2:CVPort , lv2:OutputPort ; lv2:index 6 ; lv2:symbol "cv_out_1" ; lv2:name "CV Output 1" ; ] , [ a lv2:CVPort , lv2:OutputPort ; lv2:index 7 ; lv2:symbol "cv_out_2" ; lv2:name "CV Output 2" ; ] , [ a lv2:CVPort , lv2:OutputPort ; lv2:index 8 ; lv2:symbol "cv_out_3" ; lv2:name "CV Output 3" ; ] , [ a lv2:CVPort , lv2:OutputPort ; lv2:index 9 ; lv2:symbol "cv_out_4" ; lv2:name "CV Output 4" ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 10 ; lv2:symbol "bypass" ; lv2:name "Bypass" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "On"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 11 ; lv2:symbol "dry_wet" ; lv2:name "Dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 12 ; lv2:symbol "midi_control" ; lv2:name "MIDI control" ; lv2:portProperty lv2:integer ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "On"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 13 ; lv2:symbol "midi_keys" ; lv2:name "MIDI Keys" ; lv2:portProperty lv2:integer ; lv2:default 4095 ; lv2:minimum 0 ; lv2:maximum 4095 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 14 ; lv2:symbol "midi_thru" ; lv2:name "MIDI thru" ; lv2:portProperty lv2:integer ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Disable"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Enable"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 15 ; lv2:symbol "base" ; lv2:name "Base" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Seconds"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Beats"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Bars"; rdf:value 2 ] ; lv2:default 2 ; lv2:minimum 0 ; lv2:maximum 2 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 16 ; lv2:symbol "base_value" ; lv2:name "Base value" ; lv2:default 1.0 ; lv2:minimum 1.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 17 ; lv2:symbol "active_shape" ; lv2:name "Active shape" ; lv2:portProperty lv2:integer ; lv2:default 1 ; lv2:minimum 1 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 18 ; lv2:symbol "sh1_input" ; lv2:name "Shaper 1: input" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio in"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Constant"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Shaper 2"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Shaper 3"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Shaper 4"; rdf:value 6 ] ; lv2:default 1 ; lv2:minimum 0 ; lv2:maximum 6 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 19 ; lv2:symbol "sh1_input_amp" ; lv2:name "Shaper 1: input amplification" ; lv2:default 1.0 ; lv2:minimum -1.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 20 ; lv2:symbol "sh1_target" ; lv2:name "Shaper 1: target" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Level"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Balance"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Width"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Low pass"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "High pass"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Gain"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Low pass (log)"; rdf:value 6 ] ; lv2:scalePoint [ rdfs:label "High pass (log)"; rdf:value 7 ] ; lv2:scalePoint [ rdfs:label "Pitch"; rdf:value 8 ] ; lv2:scalePoint [ rdfs:label "Delay (const. pitch)"; rdf:value 9 ] ; lv2:scalePoint [ rdfs:label "Doppler delay"; rdf:value 10 ] ; lv2:scalePoint [ rdfs:label "Distortion"; rdf:value 11 ] ; lv2:scalePoint [ rdfs:label "Decimate"; rdf:value 12 ] ; lv2:scalePoint [ rdfs:label "Bitcrush"; rdf:value 13 ] ; lv2:scalePoint [ rdfs:label "Send"; rdf:value 14 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 14 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 21 ; lv2:symbol "sh1_dry_wet" ; lv2:name "Shaper 1: dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 22 ; lv2:symbol "sh1_output" ; lv2:name "Shaper 1: output" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Internal"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio out"; rdf:value 1 ] ; lv2:default 1 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 23 ; lv2:symbol "sh1_output_amp" ; lv2:name "Shaper 1: output amplification" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 24 ; lv2:symbol "sh1_smoothing" ; lv2:name "Shaper 1: smoothing" ; lv2:default 20.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 25 ; lv2:symbol "sh1_opt1" ; lv2:name "Shaper 1: option 1" ; lv2:portProperty lv2:integer ; lv2:default 36 ; lv2:minimum 12 ; lv2:maximum 72 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 26 ; lv2:symbol "sh1_opt2" ; lv2:name "Shaper 1: option 2" ; lv2:portProperty lv2:integer ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 27 ; lv2:symbol "sh1_opt3" ; lv2:name "Shaper 1: option 3" ; lv2:default 0.0 ; lv2:minimum -60.0 ; lv2:maximum 30.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 28 ; lv2:symbol "sh1_opt4" ; lv2:name "Shaper 1: option 4" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 29 ; lv2:symbol "sh1_opt5" ; lv2:name "Shaper 1: option 5" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 127.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 30 ; lv2:symbol "sh1_opt6" ; lv2:name "Shaper 1: option 6" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 31 ; lv2:symbol "sh1_opt7" ; lv2:name "Shaper 1: option 7" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 32 ; lv2:symbol "sh1_opt8" ; lv2:name "Shaper 1: option 8" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 33 ; lv2:symbol "sh2_input" ; lv2:name "Shaper 2: input" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio in"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Constant"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Shaper 1"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "Shaper 3"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Shaper 4"; rdf:value 6 ] ; lv2:default 3 ; lv2:minimum 0 ; lv2:maximum 6 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 34 ; lv2:symbol "sh2_input_amp" ; lv2:name "Shaper 2: input amplification" ; lv2:default 1.0 ; lv2:minimum -1.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 35 ; lv2:symbol "sh2_target" ; lv2:name "Shaper 2: target" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Level"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Balance"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Width"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Low pass"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "High pass"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Gain"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Low pass (log)"; rdf:value 6 ] ; lv2:scalePoint [ rdfs:label "High pass (log)"; rdf:value 7 ] ; lv2:scalePoint [ rdfs:label "Pitch"; rdf:value 8 ] ; lv2:scalePoint [ rdfs:label "Delay (const. pitch)"; rdf:value 9 ] ; lv2:scalePoint [ rdfs:label "Doppler delay"; rdf:value 10 ] ; lv2:scalePoint [ rdfs:label "Distortion"; rdf:value 11 ] ; lv2:scalePoint [ rdfs:label "Decimate"; rdf:value 12 ] ; lv2:scalePoint [ rdfs:label "Bitcrush"; rdf:value 13 ] ; lv2:scalePoint [ rdfs:label "Send"; rdf:value 14 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 14 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 36 ; lv2:symbol "sh2_dry_wet" ; lv2:name "Shaper 2: dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 37 ; lv2:symbol "sh2_output" ; lv2:name "Shaper 2: output" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Internal"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio out"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 38 ; lv2:symbol "sh2_output_amp" ; lv2:name "Shaper 2: output amplification" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 39 ; lv2:symbol "sh2_smoothing" ; lv2:name "Shaper 2: smoothing" ; lv2:default 20.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 40 ; lv2:symbol "sh2_opt1" ; lv2:name "Shaper 2: option 1" ; lv2:portProperty lv2:integer ; lv2:default 36 ; lv2:minimum 12 ; lv2:maximum 72 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 41 ; lv2:symbol "sh2_opt2" ; lv2:name "Shaper 2: option 2" ; lv2:portProperty lv2:integer ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 42 ; lv2:symbol "sh2_opt3" ; lv2:name "Shaper 2: option 3" ; lv2:default 0.0 ; lv2:minimum -60.0 ; lv2:maximum 30.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 43 ; lv2:symbol "sh2_opt4" ; lv2:name "Shaper 2: option 4" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 44 ; lv2:symbol "sh2_opt5" ; lv2:name "Shaper 2: option 5" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 127.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 45 ; lv2:symbol "sh2_opt6" ; lv2:name "Shaper 2: option 6" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 46 ; lv2:symbol "sh2_opt7" ; lv2:name "Shaper 2: option 7" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 47 ; lv2:symbol "sh2_opt8" ; lv2:name "Shaper 2: option 8" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 48 ; lv2:symbol "sh3_input" ; lv2:name "Shaper 3: input" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio in"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Constant"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Shaper 1"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "Shaper 2"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Shaper 4"; rdf:value 6 ] ; lv2:default 4 ; lv2:minimum 0 ; lv2:maximum 6 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 49 ; lv2:symbol "sh3_input_amp" ; lv2:name "Shaper 3: input amplification" ; lv2:portProperty lv2:integer ; lv2:default 1.0 ; lv2:minimum -1.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 50 ; lv2:symbol "sh3_target" ; lv2:name "Shaper 3: target" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Level"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Balance"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Width"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Low pass"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "High pass"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Gain"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Low pass (log)"; rdf:value 6 ] ; lv2:scalePoint [ rdfs:label "High pass (log)"; rdf:value 7 ] ; lv2:scalePoint [ rdfs:label "Pitch"; rdf:value 8 ] ; lv2:scalePoint [ rdfs:label "Delay (const. pitch)"; rdf:value 9 ] ; lv2:scalePoint [ rdfs:label "Doppler delay"; rdf:value 10 ] ; lv2:scalePoint [ rdfs:label "Distortion"; rdf:value 11 ] ; lv2:scalePoint [ rdfs:label "Decimate"; rdf:value 12 ] ; lv2:scalePoint [ rdfs:label "Bitcrush"; rdf:value 13 ] ; lv2:scalePoint [ rdfs:label "Send"; rdf:value 14 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 14 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 51 ; lv2:symbol "sh3_dry_wet" ; lv2:name "Shaper 3: dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 52 ; lv2:symbol "sh3_output" ; lv2:name "Shaper 3: output" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Internal"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio out"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 53 ; lv2:symbol "sh3_output_amp" ; lv2:name "Shaper 3: output amplification" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 54 ; lv2:symbol "sh3_smoothing" ; lv2:name "Shaper 3: smoothing" ; lv2:default 20.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 55 ; lv2:symbol "sh3_opt1" ; lv2:name "Shaper 3: option 1" ; lv2:portProperty lv2:integer ; lv2:default 36 ; lv2:minimum 12 ; lv2:maximum 72 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 56 ; lv2:symbol "sh3_opt2" ; lv2:name "Shaper 3: option 2" ; lv2:portProperty lv2:integer ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 57 ; lv2:symbol "sh3_opt3" ; lv2:name "Shaper 3: option 3" ; lv2:default 0.0 ; lv2:minimum -60.0 ; lv2:maximum 30.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 58 ; lv2:symbol "sh3_opt4" ; lv2:name "Shaper 3: option 4" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 59 ; lv2:symbol "sh3_opt5" ; lv2:name "Shaper 3: option 5" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 127.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 60 ; lv2:symbol "sh3_opt6" ; lv2:name "Shaper 3: option 6" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 61 ; lv2:symbol "sh3_opt7" ; lv2:name "Shaper 3: option 7" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 62 ; lv2:symbol "sh3_opt8" ; lv2:name "Shaper 3: option 8" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 63 ; lv2:symbol "sh4_input" ; lv2:name "Shaper 4: input" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio in"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Constant"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Shaper 1"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "Shaper 2"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Shaper 3"; rdf:value 5 ] ; lv2:default 5 ; lv2:minimum 0 ; lv2:maximum 6 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 64 ; lv2:symbol "sh4_input_amp" ; lv2:name "Shaper 4: input amplification" ; lv2:portProperty lv2:integer ; lv2:default 1.0 ; lv2:minimum -1.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 65 ; lv2:symbol "sh4_target" ; lv2:name "Shaper 4: target" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Level"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Balance"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Width"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Low pass"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "High pass"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Gain"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Low pass (log)"; rdf:value 6 ] ; lv2:scalePoint [ rdfs:label "High pass (log)"; rdf:value 7 ] ; lv2:scalePoint [ rdfs:label "Pitch"; rdf:value 8 ] ; lv2:scalePoint [ rdfs:label "Delay (const. pitch)"; rdf:value 9 ] ; lv2:scalePoint [ rdfs:label "Doppler delay"; rdf:value 10 ] ; lv2:scalePoint [ rdfs:label "Distortion"; rdf:value 11 ] ; lv2:scalePoint [ rdfs:label "Decimate"; rdf:value 12 ] ; lv2:scalePoint [ rdfs:label "Bitcrush"; rdf:value 13 ] ; lv2:scalePoint [ rdfs:label "Send"; rdf:value 14 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 14 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 66 ; lv2:symbol "sh4_dry_wet" ; lv2:name "Shaper 4: dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 67 ; lv2:symbol "sh4_output" ; lv2:name "Shaper 4: output" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Internal"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio out"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 68 ; lv2:symbol "sh4_output_amp" ; lv2:name "Shaper 4: output amplification" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 69 ; lv2:symbol "sh4_smoothing" ; lv2:name "Shaper 4: smoothing" ; lv2:default 20.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 70 ; lv2:symbol "sh4_opt1" ; lv2:name "Shaper 4: option 1" ; lv2:portProperty lv2:integer ; lv2:default 36 ; lv2:minimum 12 ; lv2:maximum 72 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 71 ; lv2:symbol "sh4_opt2" ; lv2:name "Shaper 4: option 2" ; lv2:portProperty lv2:integer ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 72 ; lv2:symbol "sh4_opt3" ; lv2:name "Shaper 4: option 3" ; lv2:default 0.0 ; lv2:minimum -60.0 ; lv2:maximum 30.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 73 ; lv2:symbol "sh4_opt4" ; lv2:name "Shaper 4: option 4" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 74 ; lv2:symbol "sh4_opt5" ; lv2:name "Shaper 4: option 5" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 127.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 75 ; lv2:symbol "sh4_opt6" ; lv2:name "Shaper 4: option 6" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 76 ; lv2:symbol "sh4_opt7" ; lv2:name "Shaper 4: option 7" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 77 ; lv2:symbol "sh4_opt8" ; lv2:name "Shaper 4: option 8" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] . BShapr-0.13/BShapr.ttl000066400000000000000000000531031405711530000145160ustar00rootroot00000000000000@prefix atom: . @prefix doap: . @prefix foaf: . @prefix rdf: . @prefix rdfs: . @prefix lv2: . @prefix time: . @prefix midi: . @prefix urid: . @prefix state: . @prefix ui: . @prefix rsz: . a foaf:Person; foaf:name "Sven Jaehnichen"; # foaf:mbox ; foaf:homepage . a ui:X11UI; ui:binary ; lv2:extensionData ui:idleInterface, ui:resize ; lv2:optionalFeature ui:resize ; lv2:requiredFeature ui:idleInterface . a lv2:Plugin, lv2:EnvelopePlugin, doap:Project ; doap:name "B.Shapr" ; rdfs:comment "Beat and LFO shaping plugin." ; doap:maintainer ; doap:license ; lv2:optionalFeature lv2:hardRTCapable ; lv2:extensionData state:interface ; lv2:binary ; lv2:requiredFeature urid:map ; ui:ui ; lv2:port [ a lv2:InputPort , atom:AtomPort ; atom:bufferType atom:Sequence ; atom:supports time:Position ; atom:supports midi:MidiEvent ; lv2:index 0 ; lv2:symbol "control" ; lv2:name "Control" ; ] , [ a lv2:OutputPort , atom:AtomPort ; atom:bufferType atom:Sequence ; atom:supports midi:MidiEvent ; lv2:index 1 ; lv2:symbol "notify" ; lv2:name "Notify" ; rsz:minimumSize 73000; ] , [ a lv2:AudioPort , lv2:InputPort ; lv2:index 2 ; lv2:symbol "lv2_audio_in_1" ; lv2:name "Audio Input 1" ; ] , [ a lv2:AudioPort , lv2:InputPort ; lv2:index 3 ; lv2:symbol "lv2_audio_in_2" ; lv2:name "Audio Input 2" ; ] , [ a lv2:AudioPort , lv2:OutputPort ; lv2:index 4 ; lv2:symbol "lv2_audio_out_1" ; lv2:name "Audio Output 1" ; ] , [ a lv2:AudioPort , lv2:OutputPort ; lv2:index 5 ; lv2:symbol "lv2_audio_out_2" ; lv2:name "Audio Output 2" ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 6 ; lv2:symbol "bypass" ; lv2:name "Bypass" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "On"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 7 ; lv2:symbol "dry_wet" ; lv2:name "Dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 8 ; lv2:symbol "midi_control" ; lv2:name "MIDI control" ; lv2:portProperty lv2:integer ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "On"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 9 ; lv2:symbol "midi_keys" ; lv2:name "MIDI Keys" ; lv2:portProperty lv2:integer ; lv2:default 4095 ; lv2:minimum 0 ; lv2:maximum 4095 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 10 ; lv2:symbol "midi_thru" ; lv2:name "MIDI thru" ; lv2:portProperty lv2:integer ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Disable"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Enable"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 11 ; lv2:symbol "base" ; lv2:name "Base" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Seconds"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Beats"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Bars"; rdf:value 2 ] ; lv2:default 2 ; lv2:minimum 0 ; lv2:maximum 2 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 12 ; lv2:symbol "base_value" ; lv2:name "Base value" ; lv2:default 1.0 ; lv2:minimum 1.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 13 ; lv2:symbol "active_shape" ; lv2:name "Active shape" ; lv2:portProperty lv2:integer ; lv2:default 1 ; lv2:minimum 1 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 14 ; lv2:symbol "sh1_input" ; lv2:name "Shaper 1: input" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio in"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Constant"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Shaper 2"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Shaper 3"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Shaper 4"; rdf:value 6 ] ; lv2:default 1 ; lv2:minimum 0 ; lv2:maximum 6 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 15 ; lv2:symbol "sh1_input_amp" ; lv2:name "Shaper 1: input amplification" ; lv2:default 1.0 ; lv2:minimum -1.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 16 ; lv2:symbol "sh1_target" ; lv2:name "Shaper 1: target" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Level"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Balance"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Width"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Low pass"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "High pass"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Gain"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Low pass (log)"; rdf:value 6 ] ; lv2:scalePoint [ rdfs:label "High pass (log)"; rdf:value 7 ] ; lv2:scalePoint [ rdfs:label "Pitch"; rdf:value 8 ] ; lv2:scalePoint [ rdfs:label "Delay (const. pitch)"; rdf:value 9 ] ; lv2:scalePoint [ rdfs:label "Doppler delay"; rdf:value 10 ] ; lv2:scalePoint [ rdfs:label "Distortion"; rdf:value 11 ] ; lv2:scalePoint [ rdfs:label "Decimate"; rdf:value 12 ] ; lv2:scalePoint [ rdfs:label "Bitcrush"; rdf:value 13 ] ; lv2:scalePoint [ rdfs:label "Send"; rdf:value 14 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 14 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 17 ; lv2:symbol "sh1_dry_wet" ; lv2:name "Shaper 1: dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 18 ; lv2:symbol "sh1_output" ; lv2:name "Shaper 1: output" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Internal"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio out"; rdf:value 1 ] ; lv2:default 1 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 19 ; lv2:symbol "sh1_output_amp" ; lv2:name "Shaper 1: output amplification" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 20 ; lv2:symbol "sh1_smoothing" ; lv2:name "Shaper 1: smoothing" ; lv2:default 20.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 21 ; lv2:symbol "sh1_opt1" ; lv2:name "Shaper 1: option 1" ; lv2:portProperty lv2:integer ; lv2:default 36 ; lv2:minimum 12 ; lv2:maximum 72 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 22 ; lv2:symbol "sh1_opt2" ; lv2:name "Shaper 1: option 2" ; lv2:portProperty lv2:integer ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 23 ; lv2:symbol "sh1_opt3" ; lv2:name "Shaper 1: option 3" ; lv2:default 0.0 ; lv2:minimum -60.0 ; lv2:maximum 30.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 24 ; lv2:symbol "sh1_opt4" ; lv2:name "Shaper 1: option 4" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 25 ; lv2:symbol "sh1_opt5" ; lv2:name "Shaper 1: option 5" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 127.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 26 ; lv2:symbol "sh1_opt6" ; lv2:name "Shaper 1: option 6" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 27 ; lv2:symbol "sh1_opt7" ; lv2:name "Shaper 1: option 7" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 28 ; lv2:symbol "sh1_opt8" ; lv2:name "Shaper 1: option 8" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 29 ; lv2:symbol "sh2_input" ; lv2:name "Shaper 2: input" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio in"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Constant"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Shaper 1"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "Shaper 3"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Shaper 4"; rdf:value 6 ] ; lv2:default 3 ; lv2:minimum 0 ; lv2:maximum 6 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 30 ; lv2:symbol "sh2_input_amp" ; lv2:name "Shaper 2: input amplification" ; lv2:default 1.0 ; lv2:minimum -1.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 31 ; lv2:symbol "sh2_target" ; lv2:name "Shaper 2: target" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Level"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Balance"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Width"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Low pass"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "High pass"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Gain"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Low pass (log)"; rdf:value 6 ] ; lv2:scalePoint [ rdfs:label "High pass (log)"; rdf:value 7 ] ; lv2:scalePoint [ rdfs:label "Pitch"; rdf:value 8 ] ; lv2:scalePoint [ rdfs:label "Delay (const. pitch)"; rdf:value 9 ] ; lv2:scalePoint [ rdfs:label "Doppler delay"; rdf:value 10 ] ; lv2:scalePoint [ rdfs:label "Distortion"; rdf:value 11 ] ; lv2:scalePoint [ rdfs:label "Decimate"; rdf:value 12 ] ; lv2:scalePoint [ rdfs:label "Bitcrush"; rdf:value 13 ] ; lv2:scalePoint [ rdfs:label "Send"; rdf:value 14 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 14 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 32 ; lv2:symbol "sh2_dry_wet" ; lv2:name "Shaper 2: dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 33 ; lv2:symbol "sh2_output" ; lv2:name "Shaper 2: output" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Internal"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio out"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 34 ; lv2:symbol "sh2_output_amp" ; lv2:name "Shaper 2: output amplification" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 35 ; lv2:symbol "sh2_smoothing" ; lv2:name "Shaper 2: smoothing" ; lv2:default 20.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 36 ; lv2:symbol "sh2_opt1" ; lv2:name "Shaper 2: option 1" ; lv2:portProperty lv2:integer ; lv2:default 36 ; lv2:minimum 12 ; lv2:maximum 72 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 37 ; lv2:symbol "sh2_opt2" ; lv2:name "Shaper 2: option 2" ; lv2:portProperty lv2:integer ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 38 ; lv2:symbol "sh2_opt3" ; lv2:name "Shaper 2: option 3" ; lv2:default 0.0 ; lv2:minimum -60.0 ; lv2:maximum 30.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 39 ; lv2:symbol "sh2_opt4" ; lv2:name "Shaper 2: option 4" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 40 ; lv2:symbol "sh2_opt5" ; lv2:name "Shaper 2: option 5" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 127.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 41 ; lv2:symbol "sh2_opt6" ; lv2:name "Shaper 2: option 6" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 42 ; lv2:symbol "sh2_opt7" ; lv2:name "Shaper 2: option 7" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 43 ; lv2:symbol "sh2_opt8" ; lv2:name "Shaper 2: option 8" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 44 ; lv2:symbol "sh3_input" ; lv2:name "Shaper 3: input" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio in"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Constant"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Shaper 1"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "Shaper 2"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Shaper 4"; rdf:value 6 ] ; lv2:default 4 ; lv2:minimum 0 ; lv2:maximum 6 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 45 ; lv2:symbol "sh3_input_amp" ; lv2:name "Shaper 3: input amplification" ; lv2:portProperty lv2:integer ; lv2:default 1.0 ; lv2:minimum -1.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 46 ; lv2:symbol "sh3_target" ; lv2:name "Shaper 3: target" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Level"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Balance"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Width"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Low pass"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "High pass"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Gain"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Low pass (log)"; rdf:value 6 ] ; lv2:scalePoint [ rdfs:label "High pass (log)"; rdf:value 7 ] ; lv2:scalePoint [ rdfs:label "Pitch"; rdf:value 8 ] ; lv2:scalePoint [ rdfs:label "Delay (const. pitch)"; rdf:value 9 ] ; lv2:scalePoint [ rdfs:label "Doppler delay"; rdf:value 10 ] ; lv2:scalePoint [ rdfs:label "Distortion"; rdf:value 11 ] ; lv2:scalePoint [ rdfs:label "Decimate"; rdf:value 12 ] ; lv2:scalePoint [ rdfs:label "Bitcrush"; rdf:value 13 ] ; lv2:scalePoint [ rdfs:label "Send"; rdf:value 14 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 14 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 47 ; lv2:symbol "sh3_dry_wet" ; lv2:name "Shaper 3: dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 48 ; lv2:symbol "sh3_output" ; lv2:name "Shaper 3: output" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Internal"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio out"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 49 ; lv2:symbol "sh3_output_amp" ; lv2:name "Shaper 3: output amplification" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 50 ; lv2:symbol "sh3_smoothing" ; lv2:name "Shaper 3: smoothing" ; lv2:default 20.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 51 ; lv2:symbol "sh3_opt1" ; lv2:name "Shaper 3: option 1" ; lv2:portProperty lv2:integer ; lv2:default 36 ; lv2:minimum 12 ; lv2:maximum 72 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 52 ; lv2:symbol "sh3_opt2" ; lv2:name "Shaper 3: option 2" ; lv2:portProperty lv2:integer ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 53 ; lv2:symbol "sh3_opt3" ; lv2:name "Shaper 3: option 3" ; lv2:default 0.0 ; lv2:minimum -60.0 ; lv2:maximum 30.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 54 ; lv2:symbol "sh3_opt4" ; lv2:name "Shaper 3: option 4" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 55 ; lv2:symbol "sh3_opt5" ; lv2:name "Shaper 3: option 5" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 127.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 56 ; lv2:symbol "sh3_opt6" ; lv2:name "Shaper 3: option 6" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 57 ; lv2:symbol "sh3_opt7" ; lv2:name "Shaper 3: option 7" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 58 ; lv2:symbol "sh3_opt8" ; lv2:name "Shaper 3: option 8" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 59 ; lv2:symbol "sh4_input" ; lv2:name "Shaper 4: input" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Off"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio in"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Constant"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Shaper 1"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "Shaper 2"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Shaper 3"; rdf:value 5 ] ; lv2:default 5 ; lv2:minimum 0 ; lv2:maximum 6 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 60 ; lv2:symbol "sh4_input_amp" ; lv2:name "Shaper 4: input amplification" ; lv2:portProperty lv2:integer ; lv2:default 1.0 ; lv2:minimum -1.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 61 ; lv2:symbol "sh4_target" ; lv2:name "Shaper 4: target" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Level"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Balance"; rdf:value 1 ] ; lv2:scalePoint [ rdfs:label "Width"; rdf:value 2 ] ; lv2:scalePoint [ rdfs:label "Low pass"; rdf:value 3 ] ; lv2:scalePoint [ rdfs:label "High pass"; rdf:value 4 ] ; lv2:scalePoint [ rdfs:label "Gain"; rdf:value 5 ] ; lv2:scalePoint [ rdfs:label "Low pass (log)"; rdf:value 6 ] ; lv2:scalePoint [ rdfs:label "High pass (log)"; rdf:value 7 ] ; lv2:scalePoint [ rdfs:label "Pitch"; rdf:value 8 ] ; lv2:scalePoint [ rdfs:label "Delay (const. pitch)"; rdf:value 9 ] ; lv2:scalePoint [ rdfs:label "Doppler delay"; rdf:value 10 ] ; lv2:scalePoint [ rdfs:label "Distortion"; rdf:value 11 ] ; lv2:scalePoint [ rdfs:label "Decimate"; rdf:value 12 ] ; lv2:scalePoint [ rdfs:label "Bitcrush"; rdf:value 13 ] ; lv2:scalePoint [ rdfs:label "Send"; rdf:value 14 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 14 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 62 ; lv2:symbol "sh4_dry_wet" ; lv2:name "Shaper 4: dry / wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 63 ; lv2:symbol "sh4_output" ; lv2:name "Shaper 4: output" ; lv2:portProperty lv2:integer, lv2:enumeration ; lv2:scalePoint [ rdfs:label "Internal"; rdf:value 0 ] ; lv2:scalePoint [ rdfs:label "Audio out"; rdf:value 1 ] ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 1 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 64 ; lv2:symbol "sh4_output_amp" ; lv2:name "Shaper 4: output amplification" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 65 ; lv2:symbol "sh4_smoothing" ; lv2:name "Shaper 4: smoothing" ; lv2:default 20.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 66 ; lv2:symbol "sh4_opt1" ; lv2:name "Shaper 4: option 1" ; lv2:portProperty lv2:integer ; lv2:default 36 ; lv2:minimum 12 ; lv2:maximum 72 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 67 ; lv2:symbol "sh4_opt2" ; lv2:name "Shaper 4: option 2" ; lv2:portProperty lv2:integer ; lv2:default 0 ; lv2:minimum 0 ; lv2:maximum 4 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 68 ; lv2:symbol "sh4_opt3" ; lv2:name "Shaper 4: option 3" ; lv2:default 0.0 ; lv2:minimum -60.0 ; lv2:maximum 30.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 69 ; lv2:symbol "sh4_opt4" ; lv2:name "Shaper 4: option 4" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 16.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 70 ; lv2:symbol "sh4_opt5" ; lv2:name "Shaper 4: option 5" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 127.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 71 ; lv2:symbol "sh4_opt6" ; lv2:name "Shaper 4: option 6" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 72 ; lv2:symbol "sh4_opt7" ; lv2:name "Shaper 4: option 7" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] , [ a lv2:InputPort , lv2:ControlPort ; lv2:index 73 ; lv2:symbol "sh4_opt8" ; lv2:name "Shaper 4: option 8" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; ] . BShapr-0.13/LICENSE000066400000000000000000001045131405711530000136210ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program 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 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. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . BShapr-0.13/README.md000066400000000000000000000157611405711530000141010ustar00rootroot00000000000000# B.Shapr Description: Beat / envelope shaper LV2 plugin ![screenshot](https://raw.githubusercontent.com/sjaehn/BShapr/master/doc/screenshot.png "Screenshot from B.Shapr") ## Installation a) Install the bshapr package for your system * [Arch](https://git.archlinux.org/svntogit/community.git/tree/trunk?h=packages/bshapr) * [NixOS](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/audio/bshapr/default.nix) by Bart Brouns * [openSUSE](https://software.opensuse.org/package/BShapr) * [Ubuntu](https://packages.ubuntu.com/source/hirsute/bshapr) by Erich Eickmeyer, trebmuh * Check https://repology.org/project/bshapr/versions for other systems b) Build your own binaries in the following three steps. Step 1: [Download the latest published version](https://github.com/sjaehn/BShapr/releases) of B.Shapr. Or clone or [download the master](https://github.com/sjaehn/BShapr/archive/master.zip) of this repository. Step 2: Install pkg-config and the development packages for x11, cairo, and lv2 if not done yet. If you don't have already got the build tools (compilers, make, libraries) then install them too. On Debian-based systems you may run: ``` sudo apt-get install build-essential sudo apt-get install pkg-config libx11-dev libcairo2-dev lv2-dev ``` On Arch-based systems you may run: ``` sudo pacman -S base-devel sudo pacman -S pkg-config libx11 cairo lv2 ``` Step 3: Building and installing into the default lv2 directory (/usr/local/lib/lv2/) is easy using `make` and `make install`. Simply call: ``` make sudo make install ``` **Optional:** Standard `make` and `make install` parameters are supported. Compiling using `make CPPFLAGS+=-O3` is recommended to improve the plugin performance. Alternatively, you may build a debugging version using `make CPPFLAGS+=-g`. For installation into an alternative directory (e.g., /usr/lib/lv2/), change the variable `PREFIX` while installing: `sudo make install PREFIX=/usr`. If you want to freely choose the install target directory, change the variable `LV2DIR` (e.g., `make install LV2DIR=~/.lv2`). ## Running After the installation Ardour, Carla, and any other LV2 host should automatically detect B.Shapr. If jalv is installed, you can also call it using one of the graphical jalv executables (like jalv.gtk, or jalv.gtk3, or jalv.qt4, or jalv.qt5, depending on what is installed), like ``` jalv.gtk https://www.jahnichen.de/plugins/lv2/BShapr ``` to run it stand-alone and connect it to the JACK system. If you are interested in the CV version of this plugin call ``` jalv.gtk https://www.jahnichen.de/plugins/lv2/BShapr-cv ``` Notes: * **Jack transport is required to get information about beat and bar position (not required for seconds mode)** ## Usage B.Shapr is an envelope plugin for time or beat position-dependent effects. The user can define up to four different envelope shapes by drawing Bezier curves. Each of these envelope shapes can be connected to different target effects, such as amplification, balance, stereo width, filters, pitch shift, delay, and distortion effects and can be combined together. ### Global Press the bypass button to bypass the plugin. The dry/wet dial in the top right of the plugin GUI sets the global mixing ratio of the plugin input : plugin output. ### Shape selector Select the shape to edit. You can choose between up to four user-definable shapes. You can remove (-) shapes if not required or add (+) new shapes (max. 4). **In the GUI**, the audio input signals are routed through the shapers in the order of their numbers to the audio output. This means: ``` audio in > shape1 > (shape2 > (shape3 > (shape4))) > audio out. ``` **Deprecated:** Complex non-linear routing of the shapers by manual change of "shx_input" and "shx_output" in the LV2 backend will not be supported in future versions and will be removed until the first official release of B.Shapr. **Deprecated:** Constant value input will not be supported in future versions and will be removed until the first official release of B.Shapr. Think about to use the "send" effects instead. ### Shape editor Mark and select a node type on the bottom of the editor. Click on the desired position of the shape to set this node. Mark a node by clicking on it again (or use the selection tool, see below). You can drag a node (or multiple nodes, if selected) or its handles. However, dragging the background results only in (vertical) dragging the whole **display** of the envelope. Similarly, scrolling zooms the display in or out. In the background of the shape editor a stereo monitor visualizes the input and the output levels. You can change the zoom by pressing SHIFT key AND scrolling the mouse wheel. ### Toolbar The toolobar is located below the shape editor. It is divided into five sections. In section 1 you will find **node tools** to select nodes or to set different types of nodes. Selection of existing nodes can be done by clicking on a node or by selection of an area. Node types provided are point node, auto smooth Bezier node, symmetric Bezier node, and asymmetric Bezier node. Section 2 consists of **edit tools** to apply on selected nodes. This are cut, copy, paste, and delete. In Section 3 are **shape tools** to reset the shape, and to undo or redo the last edit(s). Section 4 only consists of the **smooth dial**. This dial sets the smoothing time (in milliseconds). If smoothing is set (> 0 ms), the shape signal will be linearly smoothed over the given time before applying to the audio signal. The smoothing time is also shown in the monitor by the thickness of the white horizon line. A long smoothing time may effectively prevent clicks but also may compensate fast changes. A smoothing value of 20 ms is a good starting point. Section 5 are the **grid tools**. You can show the grid and/or snap to the grid. ### Effects Select a effect that will be applied on the input signal by the use of the shape. You can choose between: * Level * Amplification * Stereo balance * Stereo width * Low pass filter (linear or log) * High pass filter (linear or log) * Pitch shift * Delay (const. pitch) * Doppler delay * Distortion * Decimate * Bitcrush * Send (shape to CV out or MIDI CC) ### MIDI control B.Shapr can optionally be controlled by a MIDI device. Once switched to B.Shapr's MIDI trigger mode, you can select and deselect the keys to which B.Shapr shall respond. This takes effect to all four shapers. The MIDI Thru option allows to forward an incoming MIDI signal. Be careful not to create dead loops in combination with MIDI IN! ### Sequence size On the bottom of the widget, you can set the length of the whole shape sequence between 1 and 16 seconds, beats or bars. Change the value by dragging, scrolling or clicking on its up and down arrows and select a base. ## What's new * Smooth parameters instead of shape * Do not mute if transport stopped ## TODO * Additional effects (any ideas welcome) * Report latency (pitch shifter) ## See also * Tutorial: https://youtu.be/fjhL_rku2BU * Preview: https://www.youtube.com/watch?v=DxYQJ_XJwbU * Autotune with B.Shapr: https://youtu.be/c6bUW_dTxGg BShapr-0.13/doc/000077500000000000000000000000001405711530000133555ustar00rootroot00000000000000BShapr-0.13/doc/screenshot.png000066400000000000000000005305231405711530000162500ustar00rootroot00000000000000PNG  IHDR#sBIT|dtEXtSoftwaregnome-screenshot>)tEXtCreation TimeSo 07 Feb 2021 16:03:40 CETH IDATxg|ٷ٢UoeY{o0bL!R B `7pE-ly?vwfgfW[k{Μm} 2@ БD95:7Hq-7*{ `^hJWFb)'EkKDE6ttڋe_vfmGm6l$ CPNd<#g7J=?tOF5@ X8i#@ H&:Bgs%ݣ}'hձA"~4SE:God<Pxh̠b2:RM4.W48JDګnO߶>ϨX+%(hCA&\R郛kp}ЗLr$`i-'tB+{>ȜԞjU sLҵz;WUǏ4N{ HQ BzQupHa0YE_- g!#9Wj؇do#)hQ)&EE՞@ɗ"W9ҊZFB>b\lnJBd&,aps"R'(jKS.ߠhG+)d$ Բ?+!^ @IL #AcV=P釓t),` elXe,LVlQ=/-mXTyG=F/=ThAL'Lnb\nE&kCQ_F`헋륙 pA5{i*t9IL%#'{EV=m[O 1a6A 8bD$L1k\}yMT7̥Ld 594Fs& xVr%$SI;ޱeԽOUtSTO:|LBGF䙷m[e4Ld+L^Cf DEn+f&+ERҵڶcU?Ձ6z)% f. $9;t,\@~c>Z]4%5Xԃ+ljĮ]x=YWq'UxG, -z{iSZ0ˉS@::Vm˕zL* @?2ؖK^䘉|3z)dclQ+>Z{Z!Q@mgj)K+/$2]| ~g ŬQ@/RyeTҡHZF^]_/ YK N>Ea;5 XC&`^ 5TJvq/̉3{!c iH,LTi*b-t"@ R&OIٺyku.˶۸ƫ˫aa}>z.T tE<~J!ɠ?dS\d2c$>ZX󔳍XeA$#u3\Tɮe.E-^F$ RhO1}Z>&VGp>kx=66\dP.ȽQǨRi*Rѱ$M}bi7bQ<>2)$> UhŎ"0+ia.fG(Kv\dП~g H%/S {g] o pX%kFȒWȬ,*URZ_"wk '3+}` پd-]FSc~ota 5| `:V^ՇLjB5+3 zC#A-=`'>ZɠtiLg )\N}iݬ@ D{ʒ N>:N-"{SS8w6dp8|n .<A/~P8';hG_ '?_'7\vJ rSO{(`Y fJ Xlb7hڵ/6ZŮۗziH!}7Ӈ6 >fHq`Z445mӷT E0]6Xx%e\%W^¯5Cg_>n[6naꌩY7a%瓜M#$byh2t |=D c8I\ d/p5qɱ~;Ҋԋ2'>󃢖:0d`Cc r+6Clyy\yDVDmb[DԊ?T^V5I&PC󫾠p0 qK 5xhDVVFBh%f[ˏ v `cc!rrgN8 q%ZAIe4'zC L IE )`w(bv3{#Ji=˗Cy˰G/`޹p7NFn$;%$)W$,V d'I$UPAiRNO|/oDv@0HKOes̚7ßko6|+. ,fX딳O:6E1MmK@:O alc /jK:F9ƒqG!rfZ$l项f"N<~T+P0Le+ˎx~kBnۆZdVKGx4-uBh΃D 4)"lZ1AO2iL㡑!tJY}|/xq@f`Z^Ev&YIMǕ"ɑ fiP_Y芥ÉO ?$IeAZNiYiXVXE+5bjYo5K-e{I8֒%M^e%+@0`ѼE7~Pp8xc2Q!4ke[1ЋZG߮f@)$05d 3(b2>6ZtrqHQ"Z`^Җ1ڡ0ZdnX]ej)%" N>A{y DZ-=IQǡ3`EV"2Z $~L`*WCpBn\lvF'q&;IIU=liL?]aQj#G8\R3SuUC((Brz2xzX]3H# $%bp`tA'N8-΅PR\e]Ɓ_|b}Z[a@ 0 ]|>K!S)b62y fMT4S(X.2I#T>zr˗Sjһ5mrꈟ5!VB}of{a|^ZF&B%bR!^&r1zDq ZR?FX/jŰh@/645C<3#q\fox5UW+vr%LDAôRD=/*0ԯnQ`\dQDq>wG@pBc(X! ={02!,\|z '5•!JQϼԬTxp |63k -`R\  66"Y$.+YZ8vn/ۥ'ZbY ߤ3J $鳦sEώm;xϲn:>vT_ ;]NϚΗ˵@Vk\ PO)Jd$Y 㥉Fi-dZ KBex(emW\6/Cˎ$,Ljɢl#QR{=>V[KD-s` j(3IW=TpK Zo)+N{"UgEy䣕jI!qO }~Aznf:hy#I,?0-,9~ck5xo/gw&dW}?i>w}CeWh"o@ "3F1y皣?+oH/j{{)=c٩-08}3Q~G<ȹhYc ]ִoC~ۋN )UO 'Mj F&vun0/s$7h<-zPPt*E Wjp&;{$I%}RpŚ y xŊ+ ϩ0ٜǭō`f#0y|O>$V)X{_gBx<m6v+|f5L5V| އ1Qp.ǀN_1/^8|%^}C']hf-q%鎛y\j-kWեO9FwV@ 0B,=쌨moD6Eax+?dRH3լ5k(hj!xn,De[  9f\s=/-~ۖcbJXL&~CQfKClDԊ~,= ৎpNIF' nnKQcXΤm;ؚ5.IccPdF5G$hv,a!ٞJ% ! N#)oj ж\׃fi ,89X;x܈W?N=)a ))uYlڷ8V3g_t9'3OcK)Rxc@ő/=T1jiǻЏ=_Gs&رXΡ?a-/E'2/{VEI7g,@/NZU]"3|K+u#VlX\장i %C*\.,>BHDvf6PnEQc .- bmG/8ᜌLݕ;i&}EuΗPy)]#Fңw=^2)$Fx( -NK>W1rS(]2n8ݼL8` rqLyI9I$ҲӐeYdNQUf&}y}=luiXR`,Sb-J)ވ"E,Py)`pڒH$½ UI5Jf`phC~ջC dIgq#_&uuxUon_,III,:gF6AJxƬyL[[Z-?W^qyN=TRRS4i{vaM cًp;2ضm%#/?O~KuYBMu JP^ֱmEʦ@ @uU55U5޹Defe /Po~3~ ϼ SOa+茋=6%](l,7ϕqe%RudC&r9lVG?8EAa= {ю -h)mJEmj3ϣEID5rJK{( .>CXr.4p-'"+i%cQK+'V"dxҷ륅&*I#Q޹y)kzL`?&?>C5iY~' |M>/e/ï?$o0)NoSD`0Dy[ r^ZV:lK˼iŶU ei1!1LR@b p-<&ީַ$bUޢ՞.Vl*ZUSW]D29:,X`%ZH2 ɑ9+ki̊j k۪G#9@2jk(YD3ӱZ476cvnډi3FQ>r||ҕt$I@.`j`Vz`otTAIza#~GX6lN6܊ga k̜dd@-{+Ȥ?J_5PK%)F&S9')#ЕpżryL뛐~5{vLH?S W!.&OC#Gkygtr=AzF:g_p6ZUB֕b|N8v˿W˨ mۼ }w Gl@ t5GOxḶނ $Rh0F(Ut)upv3n#8YfAُdC/n(GpN; x=^vnɊwWe7?bӚMGpb]Zd,+(Hvp%"l\-j]wiYa ;O$5+%7䇣\ic^i2RC c\E!Qs6]%|9٦ykVjcN>d]|.b{tƕ_ɧ>M`";a,>^raꧥßM6qEl2z^WKiilY;n8)V 6]!6`sս4ōXlkWN٠~-hhhЏ7T"UO+ kaa*Z2 n\V-LZtPۯL L; Ig O~  HւӁo ]xV9n 9شewQ ާ:P2iSAdd3ygyVGsSa^MuMBݬoAIJJ?>n?ehQ0j(޿|i`GpI 4~MQЇp7*Be#c.4UcN6+;To~JKع}OzF:qޝ %蓏zȲsqn]bKkkk̨@k{9^uM)cٸk~-6TWcpwwΛWm ߟ傋/e# &}@ 8V]Y $AL1ytŊlb-/nujaw2чg:3+v*I[y"3B2(“V61i&KǷC!Q犦T{WC Bbg #)d8 A|+B dhA8TpAҗ4bER*(iM=nꩧZ#GPۙBr#p'%D÷J) g 򩢎 jiZifYɦp[b\߳QJ䬑z$Jg38Fj(epW)) :O9p.X3{nX9忼/~ ü+Aob7""",<}arnӤ[,~߱pBtKZ+ubς z*xwO3|p]0tPCy7nw|lnC"G#G.j{ir}Y_934Sv>茹]NsEHX(a+9D6zDҊG I=D( >Q+0Z2*؎̙0">gNjT$\b/_}9t9>OxGCQ}/Pee䩥RIF_)owb:#S%XJHyIX`J_җLdm=9Ȧ|Ƒ)P>j(`-nĐ@0 ꇖ"/h` DC&ile/4='|†\% R޳9KK_~^/vy? m?n-? 8y'Khnؽs7߮/ʯW&ތOZv~BUe/[LIS'qM³/tGჇ3Mp:0yx酮y޿;\tE4Kcc&G;^zXG)XfeBlMl|mzE{辊GKZݽP]꽿M;g,LaZ.2g,~lCaEXT|c題zJbFlw ;YXfnbEb0^nM XV,H02-PXJF*'4z"Њ>#Yb~Ma\EBJwc%WP (^mBo_}7+/6;z;a#ҟ)súAr=bbY,s{ {)ݯӋw@ @yYe۹+ ~nHė PbFpjw'3>UahO%I"XZ/j4Ԉ L{1ѹ!0;W}Hi!<:x~DE@Թ::s>{dR:.ƻVLNJFrw0yX /^je;Ǐ/Zqӈ q7p"KpqlmHNc0%/^ZЌ| %O~|𶍻?c-a-v2)0 /nj( όHu\q`gDiSVJ1?5?~OPZin @-^57qF/ډO?;hǍ湗7^4 }`t)O>d_cWzoX1z#Jgxp!]Zok.]OQUYŜshiniภ#>:^CAf g1 zi!S8ɦjj&#i%HD`R J9jAKe1A+xw=4Ѱ}QKwlQh%B5#a*W! ^Ӷ/"Xc UF-B v\0s8\F xp*> ͯ;C\PB3uxiEyFc: Td4gbEh/M񴍭_G?^x!d.,&s)Vz!0rJs$zMu7rVSNdK55ld>RM vRA @|Sv#)zQH ৄm3Ƕx~XˢuY?}\pzsKK_n_ ~d.{3S4 f̞ѥv& x^wk.3ڈldK{}V|13صcWO&=f>a5CP0;q٭9Hc1I"2'eJt1Ll{i#jaP^mVD-m1^ZFVl/0u~/bb #d~֨h+P=?t腥xEH|Z/悙[M ܥ# zF0/nZiRyZE) $bXs?~Zhxq‰,a>`=` $)GIdlDz(ᇌ&h!,N^2ߣc03bJZhTs(>*4L)>*S$;'Wz7_5_|SO;nֻoO^.',\C>5{lfΙ)sI]xBι]}[zKi&^xn&M$IO'\{-fKﺺzƹgpAnV|A;l+t!}l-j!N ?n6XL1n\RMdD{i1wm):fF!S& &L im|@Лh@}lUzf(?JSjO>ze2onߣ=ʅ\s5az 5uU={evN6C ѥ.kr-\jkjimA@/52g,S!&M5{ڗ;Le$LelaoJ_ܛø2I6e(ura5{p@ dSD =Y1 d|lx4QU_;a,NM2jH]O]# ޸Nwz襕ɠL1_Hr)T ?>Zh$=֗|yD3J#>ܤC|ƫGM0w;Nr%h[Xf#3x.DHhdqa%\NhEVPOύX8\45K먨u=+^j%z~/ֻnn(8a ӈ@ o׳]GZzx etNn~XНy >T3ϳ}YW[#?y._=[zʊʄښ(M^KjW(JxuW֮kx^]ZWl(㥙Lc g}j(xI!A̦$,M,&"Nǽ 6n;rQq$ї[CCl`L!^EMHNL`JLˋyPaX'w=acS*^?r5I8⡅:_K/ՓcJ2E s5h$|fr=!J+:i=B\F{iE4&l8I"\._ƓI|pےZ5HNjOՎ;VWe~u\gNhHQ464p~acpOѥWW#[Ukuv17?o/8['IOϾ߰.`W_^[M1Á1y`?LC}keY;Un^veY~g#p`nb{8]Nq٧ss ۻ\ +Ǖҫ.ԴT]NO >b$3lϸRMR,X$\$3|RmQ/715"R<0V?O3T\1 V$,LdPvm3+G BiC?2CLe'p$8H.E ^<4R6;JJrAzhr)#48%|Rm[(b6Y`EK[U~ei/r;K4 "qO+— cۨرv7TѾw͏]%|ʊ:Gpmg<zgdE?&+wx0zlU;Ç~h(^vs9|p~7ٺik?NYt}\j^qn:^\{ӵPPo{~z|]P}vz~'Gd y/v&@ Cp5"a2BNqnXK3QC l//fOFtFK'N*r  92 Ml߄v {"_ %͌W2> Z>e{*v'DpA.~|SMS{KX%d%n5e \"Q(c(VRkn$3/?on|_-6 O7\?9Ձ##Yb SV\"GKOCJ~JpiL*6G嚘GkJAR}G+rq{;$hmj%9=T-M45vՂ%3o颩.ӕu{@o]((x]W]]pWQWDEWT\\.9B=If}=[SULק*LF}*TUQR]̊K-! 328b߉8K K^575;ʊK&tQVR:>j{J%­wX=n333l/_ShGKW,u'dROs])?/^s.8g "`08z%$$$$$D' 8$JH'+^ %)oHԼ"5Oj9ۙכ?PI) {L&fs 9]NjDTO%M%z6Y?/yZ{ȸ{ZYk)B=QR2c+O9&)J>BFn~}@#ca}?``py|zw4HKrϾ/ ?fskT>?׋~E0W|y3]a/mbniF kt|+o*"5so=\ a=/o7vZۺ~'~vh"'?76Wp)7]-GE{̶gwįvQ ]p㯹Կk=v>/9o\VmZM?4;Ƽe<_=/ ݍ{qbvNXM%ܼ\GhϤ 6i6-Y] vxiokO\xi|b'l}qう~OZcҊ{99W =4ZV'K+ֆ4 p!ulp**M!ePJ>%xF 1D/ 0DfTjG"Q)|P8嘅P̞X!ZbR*r[7ZSzhl;BjQ(,yUV-z zV%KHER Ew" (<>ʓK0J( p6*0cW鳄a*JyWhmBChϫcdzl n;NgK/**ųra拸+??#?| K:v>{~IC g~rw}y:,۹zK9 A۷ Kjmhb~埒_D%̙ʶwU|y7Ã#ܴ S_[@S4qW+AF2s2ݿ}nJ7Gی}C;q+73d pe==Shaμ9M{{z׿e=. o]qh|{'7t3?tG(>}d )4bىzF%zx7XT,'b˩e %T&,_D)"H;Gi`7 xL,(`#C:yi=gO*2#%*^=RlWk8ӤLrU((Ӆ/61QD{jŮ84r)|>KlM`Ą}ʺ2v}~.?{>~" wp ۞߹ 0Ws-IzfQ z-T/.aۓ}; os +C9<ud0tG=bD!=+!_4OͿM?)3~_p5^bd$2g}`#߼VTl{n;]oמzg,bN)ŕE̚SgmYY^\E8E?C($֝]1GцSh"/?xP{sڤ0 'ͭ}?_~}B==4o_晧ᅿxrt7mČ =4KNj駕TylQ Y 6Q|GN]q"4Ҩa!5,t.w L4Nz(I0Y PbO2fR\_쥥{[MMa1J!5R0]$ \Jx~jQ+7B*Vb}VbK)Os)&\EQJ=/JI!6?L | ^w^v;>ҍT/,ͧiļҳ<|_}[?;oo&(ߵ*awrYq<οf4֝oE|@`w;lm\>-4TʋɤhdK7Ƨq c|% ),??oBYbl1tsyxjU;4矞9^,AqI1=x55B]Z|"prw/egQsuӎ j/+wpFUJIic}q -ddd;c4W u_UU>tɇNŬ FAt嫖 ?'coćodfM+ ;?Y?H2pC8B7 RG9D/3f=SLP$g=WA(xH'/,Tn^칔:r.FQ~F&oiI'&{)C)Wdr6&fxyW)qsrg%Hid*V4@|1{Vlq4~~Cu5>Kx_5ܩ|WRw^>wʏ _ n~{aj\MZQ_?;T¼6נ(o`O0?+^W|`G=&i=΋)zƑQ+j[~1勉?x&?z'R v5`!(4G[/#K7~m3z^%99>^~c{㯯'y–e|?N\l>{3}跤O?4nn3R9Wd_愷wa{myy|[j!'*FGGoe]6(vm| $$$$$$N(/14ĉZՈ,Ì?nmz̯L#b%zث}D,`Yϥ\O.1LaBQϫ+ =H^(g d>G%L9c!ktB1s9O 'E^j|!U)eעaHv}f]V(M]M\vTB2JVVr*D0>Kۢ]n >Hn!ኯ֜봾3LBL7O=^u7\'vZNtd78xMmFʸͱI B'_|E1_vkC|^~EKdpnG4iPBBBBB =l,:SNBV/,w{fYK[1J"3Rx!Ylx8os -Z4ArNύ>+1:kLraSN8KॏF<( i%!+ydsb#*tq#,iw3J4w+7o,Q9Pwb91{vacr.fVe利±pBԲ&'<͝oOlLql cװtRx^k<ƕ_IKSP^Y|m=f5[qwki8샗wM޾J:z?y\VN`qk"@>TwوB1"{@]dobd4[3^'rXae38{ٮ:?](o$Lj_3<4l_b)7 { GrWg)?Cx:d =hhq&7q90}*h]ZCfA;fy\ud+trɡqaHo+TLj"2I5y!tӀB5㲎s袅 ~D90i[+y5aDcdO0H#BJ}NjM!x0A礈'+ĄI#kȠ'HCs< 4q ڦCW$=adɨT\" I^  ȃpΩp͇.;ݝNaǸ`|k7ϛl8mÏSLz-M-we7~Fj&݆ ]1~M( ߻{a3ɓZ{i9ZxiEJD#!le gSP?#E`,/̞ aˢLF,yִWW#tD %Tr בk&>C> Λ0Es_9{id01 vBI=$dbBʉ FjתẃC`߫,n` *c|rcnͦ7RtҌtJ"G%$$$$$$&E%m?r{ָR{:X7g[ W )tRDBBBBBBBBBBB΋ +d 1NrcC!ߕDrJY+;,),I}t e[d ?fxM Ծ IDATEpe":Q|s v*6Dڭ;kר~;hrף5eVF ℈hs;]Ds˩dbbmκ )g% ?*RQO7>7dL[T)1MxHYneХf >yG a W%8-;L?9K! 8l`Si7ݯ'KDMSV64A9_u+ksZq˚e! !lCA>L4s!ziLY瑆}o2=ҚEӟX ?äI!T0C@z{$3av'be= cϚ{u/~hb'~k&PI', -Jމ(üK/֮qe`GkٟGtO)u&+cv=c#?a /iT0fQ@)K 0}tF#|ȖlLoY\A.g{"អpB =E̦יQH)5S,sP>ɖ fZЩD59f$$$$$$$$$$fHg+RO3ɦ S^Z8!vaId8yd%Oj9ZZ4 "da >KT(=Fnv""%BB2񰌵;kwd圖HԲNVlm@!<3e֥14R|Y˪&Dؤӝ6H@JHRn4oF 0yu 6qsLX~Qbg L@:&gIRե~bɗKYI-gJ| 0B/9S˩PT#"{])4sQ|6ؤNH\w2ʭfmɰA lXYlCgk 4GO K42(f!kI'PM =̥(^t=Y0r_IQT&  J`EXjp#6iߘjhL =zgȦF^Kx)zȄhc[n~e/sj̜+? vxIc-gs.[diGHaTBMGI,LV0A„,2ɦ:0=X3Z 籕"^:⋬XhNx"pce^g,{yk:\(GCGS|Zid~eN+a&BYJdI-<RKܶ5h0Gul < t ԍr#j%tcoǚZE:ANʙM>x҂~x>lxHe|tr'眙"2S)}#JLT˚4vGPLn 2J=Lwh'w+&%yLrc)LHYXan3I^IHHHHHHHHL+jX\ 4xSY\ƴTP i0!FA!LjXVKC{n^ZX 6&D+ fM?44 za>rR#buDhٔC `[C԰#Kaг_"'&1.*K\.1LXYK˘VBÙQF-d&A&LsH_;RV$>rOƫNb%)"3"Oa̦C0 6wdSD'4=b<W5TH2f7͵~bݢN#\JॕwpWE+C+ kVbɬڬrPk#DnI0\WNB6r䠡%†9<9\˙8P 1J0!I YCyqT$t2# !"F&d&4sܯǀyh\Tz j2ACcNaȣ\ (#&Ԓz="G>.2/z̳Nf$D$DVb$HHYS!s En0>i0v*s@#lKAF{wuU\Ȧ"̨i#qqd֗DdS1 vG̣C93.f!V3d%yECgrI\ھ59ZƧF"|hcVN ) Ţhܱ_8DL2VSbXBI^IHHHHHHHHL)r)R*4T| $;=tA6y,`-tOtwPD ǩ'LҡGQK&D(7V=U:S@0L1&R,I&; =4׳V j觟aQ?~X zyt7˼WWꄔ[UT饆RBtv'h1IDN`ozh%4m|o*( i .E%Iń1L!B5ϐ`4&$:V:.1sQLtKG4׸p\E%D?C%rI#+VqY5#6d=]ئ}41B/FoA7]FЀJay x?Fz(gK'\4 A?d%GF%nS$YF^ݑ 1l2 3$LQ7rږlJjJLɄbKȥ”S1sz[8fϬԐ!.7QRn6ZWnHJBBBBBBBBbO q=9$&/DfsȌq1h(QQ3L#&*N,R8P9>&퉽TB'L42I#, Pt7</dSDdS  2@&}fV0/%B(*+a%OjB"b<ZG'Gx|I# }A"ȼ'xDY*٬#<" tn(C\SRωԲR<"]Ov&,!/% Y ٙr#N!DH%K4qCWSLrOGA#ݽdrNErэȥZr)z-1(.ZK8W G%/dGdhyQFETK%駙Q-n rtQN 1.S==TdM=b0z35VB^C.qhM?6NAkXF> >*K4q~KĔh|ZÖg%yzhZ>vdzE&*7m6aLHɨ)'2*2NTȨDelˤ%ѢĘ%"]>-mTҜc! OQʨa薘)`YJ۩&LɦLrYYPm\*xM(9r}VĖXv;GSNs:빀G0,i:N:C6kզ˝D*Q:yiLjS,V: (041@+&2P%sTj⾸w*%DK_[kP@jc=[x?ZuKz6UyZsff+aͤC1(Y4wډ,sI'$y%!!!!!!!!1839饃0$Nlj0DǨ` X(~w(1]Xǹtp$M=zW췆>|NA6I#BXż?~ 36ۓBQtG1f13'5urZȣFRrb)Nr#"HK&Y~Z%. c/oYF8/`[=D9?F 1H-;jPEu^)6"&2:j֧$$$$$$$$B!TRG!ePJ!edtc飓fhd4[bQD9^:{g{VBHaCqJ沆zoRbjA 6-LLu9:aw]N0\`?o-,8@-fIAve1&,_H(j1쥞#%Ntxr6tg> (;Ma'j;b?H^:hZsW?5ZLj^=tHc!h2{i%1Auql59({Ju'jޖ5RJMz?-z)*2E堥 ZsEyֱ3F3'},4фs9w3eri&((Բ3x?9 j$'QC^!†<#B3F iCΦ&6r?!МJ]qHaVDvCSCxIKduIۏb[&n=4HV6X汌9;X]1%#\a!##nt&Hc]eY,C#Qd!#⧒TPCl!Z1ք B]N3Iuo y䳂SPQk( /tr@кْ={G%LNK<䕄DeTse%gG*+J8N\E(5!B3FP5^B*UʹTr孵5$y%!!!!!!i\\ CU1mT̋pECŋjʨ LsHLjr).2͚zLMD}~͸p饀("B:U„OմsN=bȉyo9'e'tg4)rfSݏЊhH]""&r(B:V eXꈍJG6(J\NU;f{‰33eDEC6n꤅,24l%J$s#aΤpJsc- &"2'i5fɿ{R!J1RA䕄Ɏ"ʹk" LQ| f@Ċb@CËBX)'_{YN%HD4$MfՠJH(fJ!)aT1ݴNmJMgq%p .ŧh9wY'.:3%HDH^:d.ѤUDhMtnb )Esyd"9"~Z 32VJL Y^4H0)71&sMK 97&T[Ys7Y!Fa3Zq ~]%mNv98BJ9!QOS q LvB*2*8AW'1X\MKbOXt8/d2RF3x/ lc+idF#~g043yHDHB10Bk^lQN5 8 O]KTQG74s6f{e%e!,nzc G 󩠖c?=f*bk%l_41!ԑMaʏ>F4ю鍕s&2c}' =xXȹP ڝ VH`SN14Pމ3ZvzOk؄[7NOf*T# ITPJ5hXq"L*q9F}6Fd!FI#J >rX!&{Dd)E>UqLAAsYB!e I #0zK9ONFFjMlZΠbqEPB7?U8τemo|%͹8 814Sx=UmY`Y&PVQFޑ r),ă1F-RVa⽣LO7EGYúu1e1~Pu2稄G=Z2a>袅Az$R\L5E7#D Li}O7TSL%# & grYRNidޓj+8# ӳ)ͩT '{=#Ydnvo%[c~r)rJ!v% =T•d˻c6:F覈Z(@%d a%Ü VٖY,h%m=a'CveM9+z"\f&Pj6"<%:"4Z<ɢ? 9)tQJ|\".+ CgXu|֛gU2Ė]ca.衝$\p:RJ%=ڼT8-by!"FHw"n#GTs'3N )({HLX=U*9hjZZ /;Ia3+5KI1}M+,tzia ͳylE/} RFTQBL';JdL= (g8^X癝xȵJvc9eS\'A1-;P6q roK1s/.L %RRW'(b>A0 }ɝFdY`n!c頉!B8#QD9xq݄ Aü6R"]ɐ_!,'>.VN*R yKPyWl%&yqin7NHY7JHEmebR+&C԰Y0'0<tьy&+b]!2'Rrr躕\F$I@Kϙ((M:8H,.2{]`?Cf!Ua P<ġz8e,d%>x?gNԉVl hb'*!HfR zG +  %NNxrK'FOl.n%}q_‹,rrZi@l}QA'yu[VBjrRo9̦:?(a ̡Z8;YMn0KEd,N`'Rb2 6`gOs[PHEQ@ )LTaլ|F LES"*hydSǛuݴ&KH%܍,3(x8ƛEfy{/dGUT27W먈lZVԆY\Nl9z贔P,*! "BFr A5IndtQcג6!m)>5*%NR_L 3dP|ʨR )#"Ɉm:F#m424Z.1XυSC ݴE?ڿu~bhhm1+A3P@ Q^5PB%sYJ[IK+mrnm6Zi:psDB^:ۯ),p[ cVbjĽeQ{;x2e1Yő‰r~J;z9J&/L^f)RJ/& _t԰ZNheOTKfb>3RI1l^Dd螬^Z/;-V:5=h;vWZ4_a-$8ݘۊͶn)rHD1#o5_Qj,TjN8y%!!!!1H#aK:9.KY'Yr6M aF]B'Bɚv#Xs4ȡ (OK1 *9ߝJJ˩D[c ]QA a ;x^XG"5,`5dK}I;*vFH%svpA%+EaUs>s -9<2Q9h9c9Aib;9>`8HyGΤӗ"9Y,GC/gITLYI"7(M:&K<¥|,c-MSkw`[I-W+VR+fEuVR*0娄yg^ia>)t ? Qc2Zȷ 4ѽ!u܍ߛf3\*a饊yT0ɇ3lϛ ];=̳_YhY-\6E>(BɣF:D:c~H#")g6@_IJ汊33;K;-c5DcIU[["Ղޛuw޿^@c$Hq'%Ծۖ,[rS%&TUIUrC+28˱ؑՔlI-AHA@Fo}o߭t9{=sԖFZZS~&]PXGg+I6'x3fˠ7 `x >C `0*;( ʦ]||J73/ ѮW}Wf᥊ԱQn̢A1ψI)ӄe08SS[O L)+rEN{O  B-BlG*iQ~FvaƄnx 5"^2{ɂBifLA9i(%^1zL%#) w}xqBʨiFY`fϪ ;'x3fz*+Q>tL+@i0$&)yoRm 4O1^fkPB ;c7N㧜v:y,15~)"[T$/( Zy0SJ52LoăVJ|V,i䢖y]B9U㧸X Op1O-S1bT2):KSJJ0<"?>Vqa_*F+K.70aJ4RO; i.[fe1Dp_.B&&F.HIʂ~"<|-Xvqv2$#p J9rp-MS|p%RYq`1d)%XʩgVqr((g*8J~mB(]_T㑥VvHTSn9j[dZTdڂ9=r )ҋka'|3fΠwOC >B)fȋ\ɥU][_H!-5Q}fJJ])D0aZ{*ZJ>xD,wP b#҆z-cK Jixp<&L| uBc~<(|(U,GQwjb..,d0* Q1=rQ8N% {&4QD9u1LoĎZgB)+YS3(/O1 [JieÜQA)fnV)T\?䂔֏!^ [|ǒn[W\kS/Ec^UDJPL\ō ;PI# ..tqZXb xG%C ayJ:<'x,r2*-RqETiJyST}/^f*i6"\-Zv3=|,9тR4dTRBO.F) R #z,+Ns(Q*+W Ѷ#φ?U/wA;(F:m:i}1ɍB` Eʨai5/X,?~r( .4mVA*x%H@#d jkyYDz'h{gC|Ye'ɣv2Hܩ:U" C'jW>9BRiH#%Rp (ċBJ9Xj=܏ '=jYQ*=WbO$\S̓CRD9v( _N.8|Ҏ?R&HMGhYU? .(s@77yq?| ;."H2c\e%"*E%X2nY 3TS^:xjȢ/.p`1bIuEdէL>_ RsW)"*.\WF.\/, 2y'YaG蕄p=DI ɡ;AyirEJYZJBbǂ*%T"3K]bTtA+e!˃'KdMP">FQC"HҰлh[Zk$ā9r( 8=($r0F=1J. EBJOJ6*ee[n?2\Z |1hrX7U |ʯPEHтZ|rEN9FVͼxe좂Faqz<SYd1ŋB(zj*O%E>VlX`% ;dO!TA-hQj9@Xdn[KT<}+'Iђ ʭVYҊ#}xTPLMIѿ2ZVbSd(/SN-K" vMQ!cdO|.WXNr)a Ш򑤛+TW@ &SnʩLJy;)_ny/ RN_M'v"Lc!k,ZKA#6VZ)j*Z28A5ʹs0 V|Wrwb%<Sp]<+ :8T Ոe~<\89Ag,0eE„1n1.HieZz 4XBD8uF O -Tq4?R2ji~.#=z(XŨ(P:g)jZ8ɗxbk^0sHaʙ:6cQ6%RK.ed\2|{HXa%YbEXf@j’ZXPؒ_A0 ,}/pyLU4r _*h,(iZA(,L0ȯ:\Sg+ h! 1z_mvR&^3$748qF<%V' pfq"d2xG1cVͻth REG9J!yc>ffرSD4SI%V8qrL9))Vunr3fɧ*vSWDŽA4'&:T^N0dĮ>iTZCX輑7z7Y^ƸC5;(G]b 4#Z9@l/)&;*10TRN{Q_AX\b(SIpTU}깥 REm`7\Ӱ=*,Aʨ:AJk K?gx:qTie?%T$BF..hYܡYƹSNy/8m/2u҂b*8ӔR@_ihED-=A \#4q+J[W,rһ%3Ƅ0/^M--QI^=^1|&Xr b "rznK)41[ZqJs|J)=>T+&I6sL1#ytʑZR/ˈTat*"8}*gX^THo2}Qvqb2 MUfłzdZ\%D}ǃ6rybzU1a6vsry :}Q7)Bt&TPOinq+|(4Em2Ű◧t UN,>bO)e駞6:9Yfe"1]O)"4$jQ駖<%вuʩJ\8㶮 'FIC+ڂBd#E5kxߡ.75-oh=|(; zRYSF-t(_^*N2˘ sjhk_/^+b2 s X{=e x"s"f.G!S@ 4RAdM/yɎL=] O=G@31gn}R` 䦍p0tO5}O+4єp9f̔PB3Kbh!xbXYeJYʩB7^x%7Uۃ'Ă Ȥ#>9K7kH r'f .n-p)00AAnD,Q`IC(o;JvW,p0Up8m~?lع0Sb\QOޣ$*ցZܚ^-?'|ʨa^-/qsGqptAg.o HlLcR.⊚C=QK3=~KTRɞm󠙌ԈW&LPB6ٸpO?>|ذ`r4YdRŵuy8顇)']좌2b6s+ ,0ϼj|>P`<+5Л."[k-Q93~xã Wy̘ rV'[CܸNnjjh \`ze? OZ#@`@s00ti6{$(y/QL%xph"V/nV`%l_9[; 6+4\|vA _UJa }AKj6vrɣyӼUb.0B?_U#h(c"IwBjw1FE -sw^q3fFm4!GO1~!YjoY#c F=4[\)ok0 RcL >d Rui~ē>u Yvr=̓P3|/&#=4ǸC7wd 2qKfEV#}uKkRI'Zfeī\r颋 2bVXU%v3cմjvӽYak\Á cML$dA>ꩧ*ce.\2K?0QA9(ūz\xAbԲyLb)#d5AJMQda'fRA=yE,41c(O& z&IR{GEjNcRP^覝.G6:ɡebĀ*SiF RU@ >j*lEMڴ[\uŵ(cgDjOe\YfO3KS62\zz(#9#)ab˯nWjcIycqG֓Ax54G|6#FgٱN=T YvG- GV-X4{x z,r,4 m<_+&J)e?YdVT/t$G- 2DY` \`Ya[bzealnrɥ>|k2)TXPYe'N\(N:cW\Z )J=<1 t偽I[ʷ+Q@.E<ÿ `k  5r"*Xka#YVXi<L0eE>8CÛ vӷQwjD- 5q9# qd3"TK RZuTlE pZoC͚n5:9F&v&d6JE nMR3UZُ gEn@GpQ?L3*R)H%J^Z+rr jkp&j%wQI=΃g/~]݄2L0 +` Dh@8>ꨣv$bWhͬЎ;IMK,vgF6TRI;`\cK e'q0afeVXFv$6*omǍYKvr8ɧ& VrqȦ jɜʡB1AJY:նuC`bIƹK%K*hjVYaے0=(zG+SςRiJvƹ(T<;=A$ dQX'F)Q=oCnFd|۔RMMtl#Lצ>_3VR#tz^J%Lpw?;!WLE>ECdbcގ =ivp\]VX] 36pRʩf' GE/%]??CCyq?4?*O\:*h }(5AJ_ӷm,Fˢu3RE5d}\T)_zJƅ!zQ`T2&-2~cB1+88[eFSdkdǐ,V(mkJbg'6wTw* 9ϲG L'GU v;J[\/1*cա K/he?*_e`'/(#bA=AeQ?YIlVMc.,y)FRxib,Xc |TZAq3fƹ`#bZMs*ʥ<{Zv'@꥟x#!Ƨ1B6^ORO}၌ӔRM1<Ǐ 8cX.=0.S9=^y/SA=O|ӄʏ3f8 o_EX1jkyFifNoK-,P!Y1 ./qL LZai P&LdAe4LMX&,^3L`F;Rj(o %t=1<7A5՘0-Hfƌ nL1>lCB`^v\F2d2 OeT 57V[&" QNU& R,vvԗF11ښ`b)y}1,Xie?w%Lj RڲCzG^i RFxZy/SO;2[0sn25+kGIm*FE-Q=&j=4}igG6sQA=Oy7++zrlD RJ9wo$(@'gņmC'x ޢ))znޠ. 5ӓ\ʃU )[nSIY|JK0eB&䒻̲2dRF9fL" A<P@y12˘1SDYd>V7n&ď+V)DžYfdƒYfYa53fJ(1*LG!uj!LbQ"S %FIiݞF.\]& 0a"EӐxdzcTcDDҖRO'L629ϛf።<,0G+%G7*H镯Ui{j8?R؁x;I6_bFWdjTFFi*ゔqC=U,/[{[hW3f?RD @"21DX\g%\%1N.V9g6caI,dϊIlmf6 jTO%dQ8w *'/&LȢZA">|]+F"eA5Ւ,*.{!XJ6l qa1$ i ݵ&p2*Ew0ܡFL 4L_p;#ʒ+ukɴJI,Xi 8[6WqB fa eޠgef4jJhLy֦̒EKfփL2I6V2` WZʥ)8 J6ldbJ&z-Azp1ɻAPü 3ӌdy %"!,_ڧhE)&N>:DݞxɡZZXW(MqjhaIF(\pm!(iے*>OKҳ"YdO1e%b{%P:rUV‡Q$ptp V_ 88yYJq1k4젓b4(,X9'"jȯ4F흺5ゔ)׈DCSEܥ )nsuq1c7ЃeQ9[YI$#>9gJ(Zv(di0QL%m/f)~Our3 PDPN#\Px%|+Ys|?XEۦLEVPRZi 32a Nی mu)^00/K!O#o'ā+9QOwf+A|/[\e%(j R0HeTR,.HQ:Ghz>=*h <ԴTq(TEF*Dxu(چR;y=-(%24:ΘWrjC TH%^ەG{a|Hlbѡr:5D(rIw֓0~ƸvQD90ȍ8(Kpސ %'qA*~ %H%>380NSL մPNvrĎ <˲ȡZjivs?4iw8,j6#_lV1Q.򩢘FHz9#˘rdL6h 2%oBY UVrE ~Yzp3Ũlp M>n]aqbF.EK+4;9Aat2tu[4t=M-K,uU\\<9}< 7G#3@wλpv ,xQx4^BrjZ(#<͇~c¼Dպ\D RZ?bzyK,O X`+|SӅ}:Y34F RrQ]kl2&Cܤv:ɣzک=rY`^8wb@HC.?9f?q' dߡ.Oorw%i-ʂ;d`7!iG> 1MC)$hyCf,L0 '{G)y$q"1>2,l"sPBb/Al0q"\ck PQpGze;J?_(NSL-_xJEO %Tϥ_mnSEcz_ێzH[!S 3)LxGp3~/ mݔR"G {ie? (^e$gόM,ŒrsܣNi<31Ǐ6f#ŨpB-Q.rEf`%\8=Xn\0(#s+\4.n.A:g P򩦜6F)qD)Mq .dL*S`uBo'Z&+qdI ,dĽXi $"+9u~IZ=d/ p-NV (eeF%勞!OTxmŨ&M߉h7(v6׽k]-B[ e+A,2%~A(2 N:en=XIr?>B6G,FL*>Aʘx}њŗw|;Is JrADŽqb{+~g}6%x%$ =/E&  '8D0cSt4f,3ySƔ1U¢Ub]|,7~Bf`m-ByUEy^_]0yQp[[PLJ7.<)ZGlɥ;\ghjQk^u3bS-O _2NTH% X-FC3{gnIQ7J2\ Tzmm5=J2 (e"XkcUfc(#9=hą ?~|TD=0Sj>y1B?gy rma#"O((bTQax-N-|qi<,TH2a'+?Y&̴懕&x6D+&+d$#,2KF>P%)jy2r"?~'lr)FDz-ȷu)x1^QWPM3]!%KO1w?(xy `\ (Bʸ ŁD8!c{D 7xGiBZHՎ^h1L-^}pf{:͆1vSLK,0Ø[[uq IDAT Vd'lc$*H"F3߈zնo'tjXRI tȌј0_Z]T% f,D9m԰eYZ5i _$Eq09ޱoi!^0a'Kpomp?M aC3Vl"%Q.8*.V|peDl?z LSDw$Lq(g^E*( RQ)gr ^.qUDGGaZvRH96ݚ-ZqUVӄ_N-MExGiUy}YeEh pmxsf >]QJS[0q`:V3fiÉ+8(%Iz.4K9u$T@J4a]tEv9r(27xeǰ 5B!\o:b~׾ddkID2c_&; ArM TR";,0K)5P{ܣ7FT^@Az2"_)OXDPj>&k_Ln\x56A[A#;ه7c )?66zNV8|cc$Rp2D>EQU.m9 蠎6f^ r1XU\jQ+~Z{L@ZFɧƸ.o$;y 0qFy%GXѻ-pc"iǑJ]<@&ٌq[\INL=m"\PBFZTH3{1abqzL65ocA;(tsVQ@  Rɢ`-܏%Yexpa 6", S)p0I/‹{cRC;W+6 Qaq' W׹<3eehO #ycySD9ûSڸh tp㠟[|k;Jũ[WJiċ+~[̰ȡ(Khf/~fkǵ)mGmHf;J-i@_$K =$U>*6!L5&'V_ؽ( Rƭ+aC\i[4@V ~ss?F],geR @ @81xtmR@ @  rճ>믿f3v߮.yx \`iiItΠڴ"MqMvC? UG lU̩@ @ @@ @ i@ @ i@ @ i@ @ iˆWO=TRl;Ž>vO_WiSdiiiI;Ž}GvT#;X7Œ6aGQv'Nٜ}>ݎKv#aGvReG?Ž#zb*,ctpka>,o&^7!ۡ);Žٌn'''G󷫫gyU~mq8,--:N|>_J+Z[[1E}aG>vD#;NGvTډϫddDaGvlIQ|>aGI;Ž*;va'Uv`@ @  m@ @  mIxez@ @  I$]i**&@ @ ۔W5D&l777Wvu311R'Yvl6[ZGvcG?Ž#ʎaGI$U>%Q~ԩm:uJvuל9s&m,;iUaGv;Ž*;va'Uvk2*x]8m'Æ#;TTTl* Iva'GvT#; ('P% Z>믿"Af3v߮.yx \`iiItR|ۛA@ Hq \OM Vld@ ؒ+ENĎ@ V%)╝Z 0)@o&{쑄K]jw]&&&DY@ $IZ9+fYa9&ӞN:::?c奰F@  Ţwann..@ ,$` `q$e P[[Ν;8N.^vgZꢢM__wMq@ l*?9r999\r/**r'@˫??!33}kuyW n޼ɫ~^|EJKKyOʟٟŋ˿KVVV$yz)7.:A@ #a`Q$PQQAGGW\ann,nZ|gg'>}\>"333)@ o||+_ ?uM&WEww7/EEE-CCC;wJpM슅'NGGdggtvvo}1ogygbN\\\DGuԉK/?UVu=@VVf'&^z%/_hWUU?=JCC7ofǎ9rŶmrs<|QGuQ'\ԞWSH#x袞fʠ ЫuV|>%5L$$$`Xz5a@ee%W^yevΝv_,0(**Xh%KOOٳgS]]}Oxz ht>ϐ<3<ƣN0}0 *++Ynw}w຋/`t߿QGu' ?R?.\ԩSϙ3V\ je8))) ]rJrssl⋁l+Wb6Ott4 .$##Vdƌ/Xxrpt:׎3:'Obceu7 b$0T} R! ^)--ʠЫt:mLL +Wwy… illpErr2ׯ|̴iӘ={6۶m7i$y7^֭[l?p϶B^^^=łfwc4:IIIDEE ~~"3ؽy^0 l6сnz?::,-Z+Bss3111\.:;;~'"IJJ"..QGu02?~_?7x#pWU^//XnկرcK,on6mw/|wnOnСC={vu\.Ngo̜9wx<^s3gΐH{i߇3^:zxD/ugؓW9\ '9!b4vw^/;;;{JltttnxL@"""2z^xV+?HLL߿|M~_϶mxu.w=QQQ^".֭'쵇X6+&X*"fBuuu̞=3g`2 Ls rrr(++#66tgKjjjे]VV---~ޥ^DDDxK7Ϝ9>{.;}s}}7ݣ>W^yW^yϏݾ};۷ok5y5<$N+>IÇ3oϴw?:88(//=eee$ؗ=zth~ yj2)0] &WDDD$R]^/""""4^W8D7]yh+}M^;N QG>|8bNrrrDGuԙ8mQGpuQGuICbg6?G8ѤUɪ5kVC[Cu֭[9vXČ'TI&ExQGGu WGuQ'\4 rfhm!pUVV;ΐ4QGtMTVV UԩS:u*N:mQGpuQGuIF :H 5$6Hmພ@.¦u_>d3LvǀKaa!%%%lذrq:.. /rNП\<hpGD"bPIh&Lq@gh qKEDDDDDDDD1ɫPE~+(""""""""#kPWdD:tPKEW?:㠈Hh j*^<]DDDDDDDDdd\vjL'/T}>DDDDDDDD$LA PA'N.ے%K%KQGg?cϞ=3PuOQQGu&NGuQ'\mQGpuFҀ{^ى%B9̞~tu}{ CPGuvRSSDzvv{НH[/uQ';:h:ꄫ3}_2Qv^0Os+o./--eǎ5О;SuԹ|d2aq8.PSSî]r>9C$?v"b<ꨣh:ꄫ:΅tJ*CwN,5XB>[O1?N^_>$ (//j*PUU摈D폈?")B=y fp+ +""""""""}N^9'㧜\8-kEDDDDDDDDɫ,ÌZ*8Nkwvd*D>-MHH\2yu0aޥ:.=HL3/K=/kڵAtڵꨣuoiӦO:5uQgtQGuGu Wg$](DiDUߗa0 uQo!DzLh"mQGh:ꄫ:H2H̦Op38y_҅lהqF^oP3QGwL&v1RXXHII ]]]kTTTގwq\|Pb2񨣎::?ꨣN8;Zb* I`FMqUC; Tw:s)H[/ϧ:?ꨣN::$ B`'QC9_5L(""""""""dJg69\og`cœw0??s޿k DDDDDDDDRn ehh B{_.$''x<꫁- Muu5ǎ PEDDDDDDD 4s ve4}|}^fcR\\L[[---:{Xh&bccioo /9+Zll,aݻꢥzMU\\ , 66^/ݽnDQQVz***=+[0륎:455t:jr[ DEEQSSԩSx'Ng`=ZPG jff ,QQQX,l6))),X#Gv30IvKgg'n~{tnDxQGGu WGuQ'd-t(R}' \VZZBqq1gϞ +xWXdddPjj*999l߾=pټyz :vttz\O[[[ىfu^owvvb""""""""i7Cp1dddjeƌ$'''!**ɓ' [[[/?p8$''_r,V+QQQdgg0r+)"""""""2Xrɨ2 3g2| à2Ξ=Mee%^=Ӡg߾}sbKbZihh>u,z<vq Hmmm}oѣG}zD҄L<~PICuξ}QQGu&NGuQ'\mQGpuFҀ{^ʕW^ɉ'nꨣt֭[8)ANZZiii3uQgtQGuGu Wg$U(P݉ꨣΥ8q~pjkkܹsAw"mQGh:ꄫ:H2Hi`';~P} Ʉnp RRR (//r|a^*#F mD$R,f1Tg IEDDDDDDDDdhJDDDDDDDD"&DDDDDDDD$biJDDDDDDDD"&DDDDDDDD$bUAAAHꨣt^~e>1 U'999ƣ:L?ꨣN::ꌤQ5kVHꨣtnʱc"f4rTy$"2laW~lLDƪQ6͛7SVVܹsEDDD$r=a>==ȸ2JMMɓL2%paddd{nhiiiӦg+.. KKK twwMRREEEXV멨 H> Yq0b[B5՗)`_Çj̙3Wȑ#}l63w\vMzzzk?w\sΑg0 $-Z  ==~bfϞ}HJJwCu.dQQQX^b@mm-7o/ ~XX,bbbX,vꢻ]$?'O&)):3q:Lέn-[DMb6yr gkQ!ۡaybss>^1l6ZL2(nwyb!** ٌlb`2w:۟={Ǘ:3:555A}@Fed2aZnr ::zlic=O-sԩ^cQSS&//+XWQBafL&a`~Gww7N3 LLd2,~ w*XH:3q:Llo>.+(v&3yXxՉj%!!v;6r=ϱ.|ܭ ^uԚ7y(~|:c3-t`';qF~(L: GQQ۶m󑝝͔)Sؽ{7f5\ë$###ԅRSSaϟ륲8={ŋyB^3؎dnp8\ )))^{ q:..ߗFd:"ƣ:L?S@U\E3͘fHbw/kXA^>3S2c ONjj*nG '<OWtvvrptuuvqx<x':#1 Tf͚̙35k3f`̙DGG_r[8?aISS'Oɹs蠭-`\ӧIOIgo?ꨣNX:#i\?1~ff&8qʈ%==={kmmJcc#djkk{.++~?444:Ho7p=SNyG%X,dee|r "&&'tttpiZZZ8s ---={g١܌;Mrg~^0.'^oC׋v.իWx8x`g۷/v91ҥKZ444~DDDD$O̙33gdggMTTTNsIN:Esss` 477]ԍcÁ'pID$d>K&<O`OhkkxX=x =:AȈJLL$//瓗ǜ9szd2hnnرc:tF\.WG޷߁7 ,?`h9p+`BL^Ĕŋ5kV=|>---466r ill F='<&a2yK{{{ @uNSSNgGzYVmQGtQGuNW_͂ X`iiizqDՙ3gOAۃ} P:+J`ǃX:$h|˗:Le=Pu233#j<ꨣh:INN_6k׮%==Njjjȿ뿲n:lBtt4Agܹ̝;wT;nϥWǿ _wuQ':#iT ]fQGKiWWWHvRGu":ld2QPPW_͢Eb9Mgg'Ǐѣ_x=DZ뺡NSS)X 2ꨣNdwF@BȎO>;ׇS L&v;c6l@yy98~˅ ^^nn.UUUaL48/ruSYYݻihhz"iZ`ϸ_Zp-f1THꫯfٲea68}4ǎ:nwG~8rp6!$a\s53m4U>>-[Fˇ|3){v{H""!+ ;3gp~"#GP]]Muu5aidk:Dnf.䕈Z,Ybmmm{PWW7aa5=m/J. PDDBFW"""""2L&k֬{%>>0hhh;v䌝QU=ł DD6*W8q":@g߾}xBՉGu KGu黓ʊ+Xb111 hnnl'113;={&ǜ9sV+QGQ+J ;SuԹԺuHM ʑ^iiiExQGGuz;䪫l6cOf۶mCjeffOΤIhnfV,-[6ɫHQGuFg$U(P݉ꨣΥ8qDǓjkkܹsAw"mQGh:0en/^Lnn.~0xؾ};OV7ɝ`AXyJ?7NQGIF !;a>dgR֯_O!"`28BJJJذa崷t:]\.װ\<hjr7/ła\.9t⸕>eϕ0LD&,ʐtv ZZZ~;+Wjwرctww{^^ OF)**䕈yaKIIk;nhjj7ߤ:Ûf7].Z(,, pDD+5k`6dӦM9s&̣k+sfv=$a䕈 ԩS?ʕ+IKKPQQAeel'AO<ܹsyw<:䕈 -=܃j_|qgɘ1STT+Fe򪠠 :#y9zhPP'Td"f<ꨣhx\uU\+@Z^:ӧO.3, M6$ĪUx{f F}:I$f ICu֭[9vXČ'TI&ExQGG{%%%dddrx@MM͠'nrrrPQg`ap8HNN)S1;w4,X0V$}:c3Feϫʈh:}馛5"ΩS8uTНH[/uQ';3;'̙3hnn/ G;٣h~Z[[9wlHLaM,_/| {G:3:#H !`';~P} 2S]d2aq8.aioot\.|>_rss Hd"pbZGvy$"CmƧ?i~?TVVkeP__OBwOWO>$0RD&,LRSʿo<ȃMD&,m'L塺u1cʔ)Xߏ^c޽cVr֨V>+޳^^z%nv>__xs _/jȟ3?ga:m'lM_wbbbXlСC<3޽[Wc\dw24551i$-Za ٸj޼y`줶#GX,Bww7!9`Du/j\xi1=NL~&J_wdV#11Z?} L YDJg GG)++cڵȐɫz9Bww78xj~~>6͛7Kqq1mmmycO?MD&m'?枾馛0 'NPQQ;t@|FR:Sx'?ɢEHLL="2ɫ 7NǃԩSAFFw嶺6mZ8,X@ll,---tvv^rʤ$ZSQQ1w fc46'"2Q7qCI_wKOիzlذ}}L~~>'uvSFejٲeTTT(((9}?h1cQQQtttP__ϒ%K0͘fΝ;scEEEQSSCRRŸn<]zzz`bfϞMuu%^}^~쯑nM6---lٲ%b][stzDƣf#:::XVf3pjmUU/mmmX_\fs'&&SQg|w.&7f#66v[G43w].uB׉:Eϳhx +)F:ILLfƌtwwSYYO}}}?ӧO.3J||<&MI sO}͛7T$?:inn2*WqqqdggzuZZZ~;vɓ'3i$<O5@lx<,ފu&//ɫI&1s~k8)>YL\[sꙟ0z9tN}W=qX3i {x00L tuuz9z(Ν 0YX,vV+qqqx^^/>]$?111l6l6[DGݹp{mfΜ9,\[apMPCp[x 4Ҧ..((6T'ƭʚ5k4ilٲ6RS/R>۪ގf#**(RSS^5L 4;;; ==իWs~[:DvgO^ر#TL: xx<\.Lٳٱc\uUX,օo_,::K^rz}օ3Y0K8:E ]~7柙IMc6IJJ_%y<G.xïBJJJa׮]ގvt:/Y\.W/'77N]]]DGQv k֬9]\.ּr>~;RsϚP'5W׼J{l;/&//l~_:a^;3;v=ܐ'cyɉIOOjL{{;V:{UUUݻkL~[:D~g$U(P4bbb0 ^/n|||/Pfyv{,vopu$m}Gvr W橧@B=ug>|8#m\.Wlc=azD|>۷oD=ϧ|={ jB2L [ SY<=itI^^ꄭSZZʃ>dĉ8pݻwځ;)vSSLꞄocڿ;\s5,_g}6pڋE:36:#i\b0}t&))iӦQ^^?u'ɡXٳ[[[z؈ 99^ 1;;;wxu 7XVZ8v^-uuL>Ϭ [ߟ IDATk^]3'""cW^o.WЕ],m@EWN?˻3IKK ̛7G}?pH5.'~?b68q"pJ Yz5;ٷodeevjhh`ҥXVFl0 n&>L&/O] o1|iw<vL&Yʂ0LΛmjڶpQFȅXw&ui*ĕ =-܂l+o߾pM¬H;%0y/FNNVbƍa+ݻ㡬lͶo xl6S\|J7n+w?|rN>z%ȸb h^ލ{;ؓ3"C,VlE$d `̙w},\Oyy9eee}8H&&Rݽ֭[[4y%"o\N^0 bN' ~r6uT~v!w7\Dd,{[oN;mhjx;m$|mx-|[ߢ0hkkcݺu|DFk%W< DDGW2()))?3 ,nݺ.{b9*̝;~ߍpEDF7Lrr2'NwO\lue~*|g)((`Ձ=EB%##'rbw1 .Df,[}{{|={vT+"2X$k׮ ICt-[o~,X@{{W=j1 L}g uݍ狘c2"j<΂ Ƞc̘LaO3v;aDZZ%%%Dž:#?)IIIm#qt iA8={swK uQ'2:#H !d/f' \VZZƍzA{HuF3i$y x"qt.tl>}Vu3fhoopcq:DVBY @%!ʞW^:)((_C?t}>{t2o<,X0D3;V5$3푶^>/ƣ줥lٲ6b=`3:~Gee%N"%%kvȝP'e˖_<~?.=e'C#qtz?=&7&wOc?N]]qqq^Dž:y4*W2v\s5<L:Z~_uƚ.v܉a|k_#*** ~f{=Z[[=0~[|0; sVZ=V+wGC!eصkwqɣ1,A|3__lݻ~tw:uYf/~1# yyyp |>zpG"ԁhmme֬Y{82F\}˿ wX,lڴ?tww{h2 f0uT{1bbbFit""+0 ~qF?xA/]]]qm1s0Hf37_JSSSHZxعs'~[n%߹2|?1+Vd2ꫯdEpk)oxx8y$gn "", w[x< #@mm-{b&DDƲo,Μ9sAE.w^Xp!iiiD;3{0ؼy3?O/=yroKGG/`F'"ry2y:XVyV^Mgg'on=~I_ z0>ijj KD#mVkDGӱqL&^y_p8[ҫᅬdntg,=. s]w/} ǟ'x ݋Dž:ӹxh@KK w0 >=Hy|:#I! ˖/_twꄰ?ҥK੧3W L&k׮TgcoGxB̌3~:˗/ppQ><`g$Dn'%%N:,ˠ:cq;iii{~~yLGBW=v܉eɒ%:cRGuF3iI$K.ooo:v~1|Ν;SO=ɓ'LpH诳{n >O /nן|?NJJʸ|vuut'KwJJJ|ٳˑޮyOww7ݽ:Ǐ4,Y2鏥Dž:C]w guVOԮAϝ;ǡC?_W9z4ꨣNd0Hn+L | 奥_>BB 66ySO=Ess}~0{dݺu7=L&v;c6l@yy98~˅ ^^nn.UUUa7sWn! %#^'N亥Kr 7k.o$-֬Y磺]vqpMư?o{O])*0 n6ϟɓ'9uTH+"bPIeHz!y٠ bBI#C^yϟOkk+O>N\_ϭJrr~~`tbǎ!ۿ?"= 07ɚ5kggĕFk#]^ѣ$''HB2yLa tN%$$%Kz9~8ӧrǏbٸ;} ҥK?>N-[Zt:9x fUV[Z0 z!֮]4ILtww466c=f HD&' ( ||h /PKB$66YYYP5ZIJǹ\.~iΞ=˼y{G`t"" j*$V#,"::G̯kΜ9apiكd⮻ pDD.k%333p q:t.lgYz5<7x#^wԄ{X24Iq cϝ;dz>gڵL4)ģذ'X)3fi$'O/++={zp7|.>zpGD_?o^2z8p@/L&_y'>di6 kϫTTT`>‘\ex-2I8qr#x;q/b3gNSS=]wmmmCjʔ)=1Nkk+:?3Ofnx۷O:qqqNZZMMM5]U'2:~߿bV\3OޙEU9 d.(he*a*^oꚖKu3LkvʌJm} ** ( ""6/s]8g:3<>g29߅={Qy*<#<4% .^s?$RL20Nl ƎK~(**bڵr˲1;vBPP}ѣǔ[O>7KyS1|=J.i>V_ 22C]Ѯ I())zN:}k=x^OL.4wkԮSI}?0C YfcS B  x^O(#COyǙ:u**0hJJܱ4hݻw{c+@ hn,--;v,pcvQXԗcnŋ͍@#yxg`ƍ$&&IЊ[ĵ̕Tmѯ?h0`:F{&ڠN `zGeT56Hn 衇m ˨T*>'|BBBBf0eee8pIx7pwoJ(@` """ppp --M|4 zcǎ7z ̟1c*kMsY 0Փm۶6m0|wn2@ LW*T g8XFd U5JEygРAZM^gСȔqۗW_}I:|XX&u1k,ٳ''N;@ hehZ,X-ObȠAw ܘ911VZśoɹs:5S"IUUUlݺg5SxIzk$IW^,\^{M<&=q&9 )c䦲ӧOsuHII;;;*$&&R^^Nnn. k֖2bBBBPoߞ!C0|pza( ///V\ɸq㰲ĉo&} >0~@ 4%ÇٙL6n( W&g۶mOZ]jJ?* ggg,--Gxãhxꩧ$SN"Ui^$IۣheL6PZUy!<53|pN^ѣtЁ;*8[mGMgHDϞ=Yp!fB} Oo1HvzI/})f';ѡw@$$p_nDu=۷o74Rׯ>} Q'ÿc޽w8 ejO㡇ԩS\|WWWjPvݞgϞhZrssٵk] nn(!Ch4|n%zU7}}Ũ[ꅢssX yGIxڵ+rc~C=mۖ/0L@@=$cXPxZnZKu,nlIׯ>>>uVRSS|믿J(fq=p?ѣ8ǩq{x کS'y$IwޔGҹsgj͹߷ӦMJJJnҞ{Ǘ[ݦM}/^ŋw.Zͼyׯz .SPPpsW̬5닒<!CеkW֭[gT?QTXYYaccS-88K||<ׯ_Zo%%%R+W("(3d(..fՆ yy%//A V5|;߿?DEEccJ:uz9<97E?|8ݻ7ņnu yUφ ,Z)SME$FMhh(;)VA=jQ<`<Np49{Mɍ$I{h4?n>{ttFZׯps1 ...w3 %ZǏM6=z{/qiMM<<Aj̜9ٳgٶmܱǖ-[())aРAL} ::o nt%{.\@JJ zGVzJzߛ#IV[>cPՔSQQQ뭲)J?NNNx{{<£<ϔ)S1b|RCQ]v(Onn. K.x{{SYYiv痹{lllXx1:u"--UVQZZzWCi?R9.Ob&S\x vJn8p]Jo7GxV WLSt LCo zn5F5CIwww&L@ǎ)--?np)P'))M61n8N˗9x`:u˗Yr`JKKOGe̘1rG _ۛ\N>-w^Tۯ_?\y֭… rGLm&)^̮]ϩ`ȑ|ᇆyAEջ@UXsX5UM4<O 83f I۶mC$E?#{=J=OĢEblٲP#<\|)G< OIUGߟvڑo͙3g1vLxGɞ %xMSr\ډMwΎӧOSZZ*[INN&-- ///~am&kshZTڕ]UUU-=vvv3e#<ˣ&kt:۷ogرL>LN:`)0gϧM6:t-[1ṙ?RvU{RRRHIIa?N:O?5{i>jYai-XXXK/aIIIa.\ LOuo)S(s@ ?0Z̉pj5#G;… |׊Z h`]\~e˖~4 ӦMcΜ9M 4wyUת5V$1gFEUUO?mihZ}] L htܙ?OH#}зoF}Z={6dza(;L<*sA?#sO?gϞrwU]4exyy1}tFAyy9k֬!??_X#t|\v ^}U# #୷ӓ46l ..ZIIIaii٠Z2#F`ܸqt:֯_ϙ3g$(`}=z|ŋy7ӧ666r5Άj\ cΜ9X[[SUUņ HMM9;vDsErէOMN~~>V\pHc F0eCAAV2f.6uA5>>OOOKtttұ`ʜH ?  j233Yp!ÇgĈ^ҥKٷoMԼ.OaJ=:vȠAGw\ojٷo[?)i\*l2yͣIIIȑ#T*lmmw 8˗/(=Qf WL3I* @8A#G0z>(kk;<{Zݑ$SNݑ_ɉ???Y|-.QիYYYcggG`` *Ս $$R=rss+ n,muAv F#.\`ӦMwZ[qãR$οZ-7VUU[/ưqVqqq!--۷iZZngVTT+mۢVIOOGדG^^mڴt***HMMǧUNN-,;v -7#<\tW^yN:իt6T*,--ꎿo <[^b(..䖿j=|}}qvvHyI={6...deeb jROcooOiii֭AAAŋtܙ24-[hZZm>8<7n3/2bm@!<<3k!+(.Uo,EEE߿ѣGW۷g̘1TUU?$(ƴ? +wzٲe 4N:1k,?qqKttQʘnnקߎ 5 NoFZZ8;;NpFAVmCH`9r۷hxwpww;@֭3f@$[$(x$IO>Hr>GGG>#9wgΜ;@`ǵk8r-bÆ \z///y.\oXPC SJ*B5tb<#hӦ UUUlذ|BBB jTד@.] T!88FCNNiiiM}a˖-2e ܹs +ݻWHڵk8::GRRܑL}ޤuJ@P?D8z=t҅Ν;3tPBCC߿?Ν#!!͛7)w\lc`C*ؕhB?dyT*gΜa߾};vA:9rl<_ӧ[oŬY<A3"I̛l IDAT7ۓ7|#w,,8}4hūqCzz:K.N+9{,gϞeDDDп'""Cݻʒ;@`VQj(J{e̙T*~G~g# NII +V_+,YD 4jwy0갫WRdAh9?0`|gU[<# M7,GcڟWRgIJ '??M6CXXcw} l۶\ fEjs7\p G:TPP...8991fӦMv֭[+988p5OsKnxG$66Aa]4G$4 ZΛ+lݺ$t:TTTzyޮ䄋 #`„ xyy58Ӳ>Hbbbw\J(ěנaJŅŋӾ}{Μ9ڵkI-0)aooᅬܑŅw}VEJ 0\xV˽+wFV4i$gQLM&4=ƍ3g;v 77kkk Xr%X[[Y hV̺xeooϜ9sXz5nnn$''b 2TUUvZj@ h8̟?paӦMrGL$IHiJ$~ɚӇb~7fCc۟Gnn.̜9iӦe._5L>m۶g1iҤF͑%j4: @JJ K.5ۮSRR²e˘={6}W^arkkkz-:uDff&V-,UojN:#aڴi9ׯn:A $1i$Ǝ 0l25BAAnnndE@R1\ 8W:c@'WJaa!Gѣi///>\\\pqq!,,t.^zO.(+^ W,_rc Z.\`ݺuocܸq<_Nh)}QO& CJi%Ljj*z+))u~ޮZ=,YV˖-[c.Ғ,?0{KE~p%͛W/qJz߅Gx1ykXdyGy777|}}ر#}17N#11D8p@sU1fB  xxO?59l0OZlܸQL.P*SңGҘ:uIp!+11ɓ'*^) Ξ=+s֋'| ݻ7I h|||1TN9s&L6$#Ixꩧ(,,dɒ%bvi4偼c $IA߾}ٳ'jgzرc>|l S=as=$(l"Vu(NǪU1c>>>̟?3fPXX(w4ٱbرcggGll,_|h.U?X,Y3| .G hd3;@FJJJ5Ljݝ`i۶-ݻw{H^ҥK߿X222|XP`rWrpp`ܹуR֬Yc Asqu.\+?<]veΜ9TVVO hRY`dggIBBeeerGZ5:#GбcGScСTVVuVtu:r(rΟ?9p666Э[7pssC<,qu/^L~~PҜW\rT{ _:@RGff&?@`dh2pp'We-NGBB ڷoO.]puuˋpppk׮<HիW"66drrr8{(j jYW/5>foo '22Q`XGxsE/_ԩSyG8p ?3%<<$ӧpqqQDSy:wb4saG5#<7ӡCg\z/// )ڟ~??? ?Q_R (cLNdzғ88m'''rrrغu+$3ݺu#00:舓kUUdggs%,,,믿6j{f)^YXXxXXӦMё">3? 4'..?'̳>ˡC8x,yj$_*$m<$1c Lyy9`ӦMFM̮iOZZ={d,[Q=3cmm_^Wi<ƴ?+S撛Kbb"[nERѮ];Օ닷7^^^0)g_Nbb".\ڵkHqq,%<͋di!9r$- hZj [ڻV7ƮX%<#W^L<JŪUؼyQyWPTXYYaccS-88߈Zo%%%nmyL] JŹs%y?~<=eee,^ .~~ (J"%%Ey=$ĉ9{l=ƶ?'N8w~OKp+ݙ4OQ%΄Z-w/w|leO~23,Z'Nvyc-x9v֭ȑ#| __*g}V32\r%r-\`0s>d%Ĕ Us/ύ7H(&V+?ӟhkkB=48CNMHepZ"%--t'--b à,z؞[mmm477inngHss3Z[[iiiw?T"puU=ūK.. ~Es⇨ϟ?ʶmp\tvvH96oތf;馛 /40 /smaP\+!}~o}իW%L}g԰aÆ {!DbQ(51=-HU rq\deea!??,q8̜93gIff栏S[[[ikkfꢳG{{;ى3!XLB.#6o6|@frhL0g޽~8s {n7-rֆAnƌTTT`Ziiiɓ^y4%Ϲy7hll_"gEff&O>9KKK!!SbO<6gxJKK>W_M _d֭455ϗ<$cZq\)OOUU555̚5˗syF 뮻 aSGs3SkeV` '՞y)^7z|P#qkK3ZIOO'==sD2w\ΝKnn.9X @}}}a`ٰdXV, v|.]Juu5>AwwwHn|>===&-$D|ٳ,\Gygdee%;41={6/|G?t~$RˢZ˥^ʵ^KOOOIѣ|fڵ\y,X_׬[-[ޞaܹ|_ /l6SUU/D455q!Xf ʸ|fx{D)!?zfgb  [qf7ix).8bVn9.hޣ[XxA?;;;yAww7ׯo|x^~>=ۄ5\7Mf3@m۶nݺBBɖ-[(++kw+Z^E(,a'_ -~+d&HQ-K)^ E8qr ^L׮}m WBttt_>|Ϳ˿̛7/0 ~o} ̦MW?•;vòe(,{_ wA8W^#τcĺuqk\iZ% ! u:l |pVn78X%|<:iъEtuuxv#<?y$b0w\*++[b׮]%RpBvEUU^~@ 0/$f"###*XI'''tL&SJē .wuYYYx^}YyzzzR,m`yxby'2;ƍYjr K|#w`\,_RXX2Lロ+Ӊ駟f֭q~GsɓKnnn3+'y'-< ;<B j à]v/j/!ܹs8~xR-7pwq;vW\\o~4Q! !a<'=k 񓌟yn뼍0%X|0ainLf0&xŭWpX ;Dzzzظq#[lUVqEq 7p%p80E(**c͚5A 7P#v!H"O_?E]DQQ#ߗ% 7ٳb(ƓڍvEky~CC|1狴0R"Ʉ``ppCr)[2"ߤLB$cǎQ__ѣG/ݻw';<1F Yf w}7nG}K/DUUUBLQ<۷o/3 ?OOYYW?Ob"9h=ȧ>Ez(@c&R}UGs!REg~ڢWBLK..rdff&;D1n7?]w&S;v෿=̈́bꫬ^n'xbU +_ ak) qbDF^EG00褓&!YC+! pSNBee%-"--!--mˬb/擟$+WdƌAnΝ;ٽ{$H]N:G}Dee%r =/vLbB!HuyPO=dP@ S+!A"?w/`*N>Ͳe())STT-B]]f Y)Knv۫FBL矗^z?o?tww=^w2CB T?BKdClb8|Oȫqa)--Gy&//۷cXˣB/_ƍGUĪ࣏O'//՚2SVVe]-Bzzof}]yN>Mii)I#S"ٳg'!4668nDWii)wqVnG}v{=3Uއ3]=p&S"xG}Dyy9~;O<Л?|?|ٰaØbKKy%ϹGeiз-M6+X&6d&,r趻ロ7&!#d2aq\8AqWi&<ՅS\\LII c@;wgCuuuRεo_ 2Z)}M&vEqspBL&pӧOgv &x!!2رcId|oZ.rl6_~+$;L!D JBL=s7wp3C'KXB O$ \T !$o>vAww7]իWrJVZE8;v'tp\u\˵SgM (u^I, vmX@ ʫJSSӄ"QTTĊ+x9L̙3lذ!! !"E7j"V'4H.\%l`g 8G1 cKȑ#<,]ŋdkiii۷CqС ]*^1j>|{wΦ /^Lyy9p8L(ĉF !ǹS؆/` E 4P@r)jc4Jxꩧxꩧg,\0Σ0hnnȑ#:u'NPSSCMM gΜIOVf͚Ŝ9s3gse޼y}Vɓuowz$_1=4}@,!BDgD U |l>&I@CC[lzG)͞=ΝҥKeŊ\|}:hll>Z`ss3MMMF{{;g\SVv;iiidffIvv63f //c̙L&I08u|՜ GyS9#׽k=7{)]#ϑz#ϹGyڞiʃ%\ml=6̄EWN9[vw{n`\#H<&&pe6\p 9={P[[5zWWnF,1cƂo֮ͤ͘}o=<Ì ߻jQYjcv-|:{(Og' S7&̧r0n"&Nh„E1%Gy)ϟ'Dc{^xUM5B(JKKY`AիqIH!b|zW]ps/&;_ZZfc˖-n-[Fgg'a`ǥ2g,gX_̧:?g @WW׀|hpRbBLOB-*H]#DF^)\O2(Ulߜd@vv6a0 x顧z hiisݔvimmfÇ VH4=Fr}RlowLSBc!xg{[%9ŹS؞Wq5v?~Npb ^y啃9B"zQ'Yf6t:1x<趎 /i\r pM6͒%K8uTٵk˖-c6+0 cBy0 , V͆jjb~N>͖-[x7 ø>łl6p8HKKK'33,S"ygxGdyGdy8tPv#m_5ՔP29+Ʉj'PZ @ i`0~ӣӭ0MMMqGh8q"Z8v ,Pr>X*sNxGQGyQGyl}nG R#"8ؾvi#̄)-ln;شi`p\Hg$YfQVV@kk+w+++0 8rXr%7ofϞMAAAtT,k׮h<-" rwҸ ټќP#O,& ÁrE/o/^kټy3׋륫+z=r{鯩`ǏOxGQGyQGyiii8xbg}LȫM8}4O/ 9z U[lxy'Yy'x j5)S!us%##׋a{nzzz顾AWiiiTVVviii˱Zs)Z;o:pwB!Bh&}e](++inn؋/Ʉ餣Ӊl蠰00 .RzzzظqcHuĉ>}v.͛7`HhN%WnsvxR1c'O`6 }FNddd`2p8B!ijj :TѣG)--ȑ#q㙈G1͘L&fsPG رcQ__j%  0vV+.+=RJy˅ffD<#(#<(#<ūeRF^رcܽF3g@okΝ@hpb zǻNgg'}Qt[wV,]]]8رc5y~9=rr\w:n***k9~8;wǃ륧Kgg'^7z{鯩,XAMMMJ#;<777wΛ7͆j=Q!FH{0B!BD`LJϫJ4Dk vc٨ѣI. ^eIebK!&!DPBLu*tuuꫯyL >m۶A[V B!B! 7ep#Lӝx#x!B!"ִ6(tC5l^<BG,&2߰kICyC=ĦMR&DyJJJR*ygxGdyGdy&,if9%3=F Ax&c2%>j%Oo <#Ʉvr^YYu]gÆ 8pN:;;zqwww r^aL&=#<ǣ#<$ˣ#<$ |+Y ~+[NNϫ@ 0nO"3]<]eZ1LyJO8& *#Gy>y'Yy'Y׊ϳ|hgH #̄M.ln[yWB`2p:\.n땕\k|tvvF/^w.BPpxJKKJr$B"Y(!E(God%?X9|:2̈́BS68رB!B!D"qNb3iZx%B!B!&Y$ʹFgt)JK 1Xhh!DPB$ !Dc`p1e\EX0szMx~^#!Ox!B!b'Ylr~^g'AT6{ !B!B !e,A;^ֳ1)ū"jkkGxbGXݻo2(Ozz<ȓ<ȓ,<ȓ,O,29@[[Oa}}L8fҥԌ˳tRygzWϫTz~0D<#(#<(#<F. = ~RF^%z <3 /ZBsO<tt-9ƒj%xWٷoN:;;z]]].rMTUU%9!tCG,"r|'eڠjbr'RBd#H?Bd?Fj8L Mc/^LYYYUW]Ezzz#B!D >B!ĹʄƳrY"`ڵp \y̛7~˹ꪫ8xrbz}}Yqp,BT??꫓Ơԧz3gΌ{gyF,!BLJTcǎIKK /􎜲lܸ4VX㡹9ɑ !bb6 Co? !BLMϫuǃt1 "v؁QSSCQQQŒΒ%KHKKn~y\pl6jkkٿht->yBLwWEEE<,\z)6o_e]C=o[|GҥK| o磏>n/}i~#pa{9~|^~e^xR}~ _ό3xG <*l 1 !ĹʤrDIzb;ُEQRRd:\.f̚5+0 VZEGG:3g䢋.رc}+**w`Ŋpȑ>ǔcչ'hdLӒ8p`@ b㋽ft:/~ACC?я'?ɋ/_UV#DW}D=s}(m6梋.`߾}̟?S__իq8=?c%9އs.zGihhgtd+O<ׯ$3acJmwǁ&\\\Lee%;]p۶m<. GUUn+^{-Z;wnSPQQcǢTTT 8pW_}5;w .R^1P#O,& Ӊvrc/>7x#_9pmmmx<:;;\^ozWWהypBf̘ٳgS"ygr=֭ذaCVZŗ%>fܹ?b>CϟOmm-TWWsϱrJR__uX,9=;pA駟GСC}?|_x饗曹 xGx}Y~ḣSGyGyE+Y ~aRz^=ztܞ8?LJTVVp8tvvHOO6hOKK=|񮮮e٢ۡ8>r^C!<իx|., Y7|>˗cilld߾}Xh/#GPWW}Y\\,Z[[)**Jj B!J6pOÓLWZd 7x# {*Hz^ .ID1PB$ !D2m]WbڠB!B!LAM9"uLl`B!B!D2WVJCyOSvܙ2$3gΜGy>y'Yy'YdG^a.<>yghH`DF-((}q^!p8p8)<3} ]j؞J… q::u*%GyGGyIGGyI'D7lB!B!>$x5!͘'B+`Yd`B~#3?ʂa´K.1㣇?4aV&7NxIWRcܖL6h=)1L&dbD+|I#u'y&nC$Il L;8b:2!ū4&B+a`Y`1 q61rb{=Z.3MϱDR.y ||`ۆ3|m}&)xDba)'imu 2؞i5}j";[q1AN Xdn?σ ~L-"u#):bz#->lˍv Ghw|SUfQ@,TPQTV@8E ĭ[PP[= t7I&M&iV+{Ϲ$|wNXp7H0\=om|DY}F_}i,Bor'#"|̏iqAe bgKDgWUӉ\$ g=j!I! 3'*E~W/"a@T#""2cСa?Tcs7xLk\"JL9@91Ă0EZ$Nj6gdA*>hhPrQw6ҨCWzБQĶKm_%ŨbEDhר5C9 ~f},͒%HEG4EfZ{֫|QaJ$~1aOl Ydddu>&^ߚС&gčHSۖJx]q'2AzL(/7n*ÄNSJEs3s Nڑ@9)tsP"ۃ5 =#!QBUUxK!eÑC&q(! /wEz.##1bLxAD .p{p*% rɒ3o{e }MjD<Ny`k̆xbj-}@ JS;/S \ iނTÊJH).Ƈ76 Iv/oPF%t>O-%V2yo9V*,-"###Hd!' [TC]lj oP(J:AѢ@č' U*\PVx8Uٌ IDATPB) 6Gĵߑ!#:4p$DlnIRkD\&:*5#l(UX& -Fd@4S#h!HxϧQ #N\}*Q`@ CV9QL#U3È>z•dOzP#@$'`I&Y胬u5$:*Y(PĀ6.-/G?MjhnXa@ %Tߣ>tQ  R8=L&thp= ZxdBq⦄Jth0C\pkp+"A Tr+6eddd@*"LM P8Ζr9^D/VW #kT,Gbs#lq[R5@w[ X҃{\5jT(BOU li*ztWNMߞһ v +0ǍY[ *eM vlѡE%|N^^"bP2E v,d=ūfB wiO@ jٲ%0|U4dQ;QAvl"qq g~ɵgj䒅 v} L(ūDӍ4; DH)U3_ 583- ej|VS Dth,kK ;NlD2222q W:( 5axBCżѢ& O('@*p}yAl~DߊﮩMҬ9Z~M#FŃ7j0K[yAc]F=PX47l (1dZ9E`ޤ@D\O.5P^A`0Ju+u8TUVaX$ڑYj(k':_;,E4^#x%9)刐-݋t\f.g m-՝`\4A.̊5*(1aa30z`-U '=*(Q"SJ>ƃDftve_i ytލܼ\L&* Il\.$IB$IbPQ^Lwed҆,^5# .Thk5e"@MhM&V(Р X¥"PL%jT0Pފe˗ 11bY!2d+ydEuZ $X8dLD9frB<(:,u8@%f慌L٫'=z //ٺu+UUUl0++Ah4FIطgvJdIsg:<Ǘū G -j wTR6lPZԨ0MSHUc&k'PD 0׷LST(DŽ좱XG5˿I۵nG7nԐ2^H$Z_FȔoi>=m ń >+S!@j>Lt؁M6TTTPTTu [cl6JJJ<p w4E#I%6Yp;J s%:Ѯ?-Ft8}A-eKp7;}^AQ>;}ЀMAue2J %jk|_d!4sBy\1i#9C׋eTWGpdӸ|KDX~W./[hĠkg=7J2 ?7Vla@ QZ) g\Q5N!HuTiHޅ,#wơ(X9xz.grf<+]|̌㹐{I~];ɡiH8q,1-ԛ3>&%LzN X!!CCDŽ~W䐍A9T` 37x^*9oi&QoE魵ϪVѮ]tw%٫'mڴaƍ\VQe JDQD˂Zn= 餬 >BkYT 9\1 JK}@ O`?yf3W_sǔ;mY;n,,q7#Fϵ_`h2i?OfӘ+WRQ;#lٴc;Kq9ɶ)*=;ٗ] n]/WvIH4H@U|G`D&DK[}䒅-6IʯdeuC>ipaOMc-y1kJ(z{6^g0󩠒Gqpᮗ7@2cZs#"RN5&%ʴhY$ -d?ΗF.ܾ{(Q@5*Ġ'.,0ulN-myxyIwL4A GMww2JOӣGJ^xtw)ѫg|W cT>u~@ف0<]hqe(on%"" 庑.$Hؐf֗Ӎd) 7kԍC)o>bT[۶mԡ1l.{p$\X6YugItN\R# w`qu7w2?La O4 =^ h2ɜȉa5cZDڳ7#+fd1 fp&HwrJVZ$thD6SN9SO=5zӧ7*7S+fhPC퓱hqƎ&m} fpuxjcűV ):uI'(u]lܰƨ F1p@:tlfㆍ,n9 ^Ȟ={1\yՕp JaMϼm&L@EI7Oū ˣ(`6ӮFcU]Ѫ> ٻOoOn7\ 5!-s應`U?Hف8wĹ!ew7Ѻі;vp-х.|ʧle+8w$N с)L򰺑kL,DTA.Cd׬@[Oo)QR0RJ&`g +>soۮlѦW YŗŮشqV{f46l1!qv:he R7VT`ņ%p;>8ȟɽ܋ F]au )d#p>*>T(bYHޟ7^n㮵@z;8?Uxk9Aɬ!|ꩧbnGX1ǏE] Kʬw<#1k o\*S}%$xp͓]LnIp:zJJJp̱0󥙜<(S1ALc2QY`t˟f, q:VW7`~^W ŽPJz,>[x՚iFC[)&YRŮ @piTW֨9\qWDsF~>QC8׿9وlfͨk1Va$=@]HBLD} ڦXW2䵀Qw '#IMoh4RSSlEP)dp.Pя7sޓƈG9:oミ`}cٻe˖ ݎb{t=O.߻v']tomm۶m۶Fk7dӫW/&2/ t 'M>}0~xf͜Ŗ-[`?".wva͹kC@N+]p+@[E[%oXGj.>bֱ3t1nA rx/6l6ߏ. /Ȩ Fq ~Jɷ3( 8 O?cvt:G}c=., \7hqcx4 so`Q-fk!IxHuN͠Pwi PbN oQ8=FnT{]jcG :<{rp8078bsڝ˂/L %׿4w0l+<Ka^;Z=!Cǻ&F1:lq+4hb2#QAgu "t.v9Wqͷ+Y#Yd}QYL[2{¬(--M6򒒒~8.tcڴi S%2+IL[n+.agRJQGoҶm[ndvEA8 @'ݞognL'Pï/Kւ&0_%kY&?vsyq8۷}v/ZLϞ=, 7˾ .eߦq#xxns<ȣ͡1ٺe+#FUT\uULm2v{ft`0\@VVVߍsA_UСݻv{Uc`0`XDEˈ`\s5}OҬo+G7yy'mY;n,ϿJcjL&>DDtX]Q4-M8'7{UU<ԣ-}/JW'Ow'=nvcc~VW6u#HEM&/ سg/$ˉ'{_Kp~!~/UD> 9_:4qD^x{ƛI^Kqvpׇw{8cc `l>.+zEسgߚGvsr%\p ?3sx-lxp>#`ojPxȔXNy'CO;4:u޽{*o.?rʟ{]H~i;".]sy1$&q~y6_q9m۶eKC1)mb.-m{>דηuFrIl{hXRfwxqz mh\A4qǡR[h4 />Ϥ ¶oݺsGY37~ ]Ȁc48X^t!> N^狯 ˔Uo,GGFNZ0LZ; O]b=VV^/ Rc'samGWuWYѮ}xUQQAnnnxFj ր| ]vٷl޼9_YY߾-3 [_pv"PjݫK}2o,Kc]Hp!q'%A$%jR=ʙ2Ŕδ_ >BC}ߜ9~2e}VkX|S/<r Qţn ;"8a X"B-o`1/xa1Y21Æ zP-_FAAAV>}ꛯՙΌP&(:+@}]#w^%DSceȾp a|\KQ(X@Yii)[l~gڵan&MDǎ9~m6s^yyy65A`S,i۶-s^CLzVl6#YYYL4ՋW9t(ΥP(WLQaVUs '6Ɔ)_$֨[x՚qܬZzg#dώLerT-]܀x8(Ee8=RQdVM0Kr"dozKB$:*IĆp& IDATm=ʪ*xך޹ o*oNTL̶ܜ3?|m|gZV_Gu$/ (r+:A;G˂k0vX~w^z GKX& 0 >#lDQw7k2n8~wΙAā?֕[vpp{i=<yo{/1M&O={^Uc]QCD̘E C+YlܸC7*`Ӈ- \Ef(Q_Q׌^lys•B@\,]wnO~҇>c]F42eݺu[M7Q]}lUIs6m#f_q-7o2Y sN^x>4`AA}#Z(]( 4nM3r9 >7LokH$ QAEQ X,hInCe\_OЬi?-SmJ@::w鈒 UByhƋ-J?Ӄ d#7kffĉtZG%k(ʰm/6r>0rJ.]xm2P"޺iMc'ؾ}{m jO|LxN{Nٯ̋QXOo܋ϱټisR@*АNٯO70a-/>WiSf"{D̘o7oy ^߿o rxt~K2DDlF1Et0vLј% Wd3u#**VVoq;(6yɎ˽)0DĠ-]ƲMYZZf&ܔ 0},6)..fwٵfG J"ك'$v&T PpF/Z܃k4~ٜiMvr)b Љ.j<`{G:U|Kj9fYiVp2~{ 7SCG1-8z\D^+ovOj`; ;2/*uϔ~JABcƉ?ʺ• xGd /|}þ_}^g欙7hѠ@ąG T\ws'>3u򔨏 hQ#!%}2_*..:d* ɑae&=cwL?_؍sbߜ}8c:!ԕ)1[.όݵ7UhڷoϮ]Xjubp\8΀pFjBnq:!~4f}%bӻBm4˫+F_AIy (;a lg3̨>|krGu-KcǍş0rHF2Lf1AG߾ɾܳ2\{auǍ&FdokK0{\1fYw+""\H>+^9}OWӉN+zf69CC$t}ZF1(1Lߜ!A"ԊQ9b MLߗȉ'K.FRUU;m|dq.F/BPx$ ue]S$' ;︓C1d._ U}m$*1ӹ[gnv$I7<ީwL _17UС@ĉF >(6mo߾|Is[n%cǎeΝ c@z1Lav:-jFnJJxe2YrYJ0V(17qx(:MY3r " KO ]TwC:lXR:vHAAA0eNyY9Ljo2 'Wnb'` X"FֽUQrt[V}Kӏ~&{?S({̰mQݒϙ|hI`lg * _[w@h冐 8# m7ĈOd'.zәc虸 []9\ZA*E'HBcSQt|tҥ}3s/x{n_\\̃?x-rrrAn&#9b7b˖-'*kT썺vޝ@uu5[l?Dt3gF}ρ*bG@`Dv,~(@>&Ԩ TW&Dw閌 lUm#8aK?/{.Z+Y&K+ \!xcZjlTVT,Dz在Ujnw@,`ZYlY•L|GsҐ0%[8z+^y z+V5;`Gb:fё|&xkFb\ȩIںN#R+iDӪl횵xw"վUX(;hG`XcѐGmZSBeXTW&3ǸjӦ eee^ @ ,cU].rmEْ8aٽ'=̞=~vaiIcs}p8!7;}ŘfjuUcb(ǵ`] b0n;ϷNu"+ 73RhҁVe䨑! 뮿]4 Cu9ZOLPk5 u>6Qk5 u꠲HbE^!Ia1zzj_rC7T@Q [^y,_وz'\j.%f:1Eڧoڿjhߪnz*[V R+_AP3hxQye+aw!VWb~:t cn0ۊog׮]=/x@?:,ė}υr̔RР&#%&ѢA %* * h@iG& a݂CTP(g?c 9mHXwP5H. ] c,7kjzGt }K^^Ŕ/,U_u3:Sd?aAp[7oETTOV$ItЁҲ=j:dZ5`?:!@fzFrl?n,$=n -+Ǐ ~erG\5.Tn ^E)U`$oF 7QEn:b#Y\ Y5 o39rE7_:҉ iXdw뺢u+gC8ʫ{X:v[ [V3̎9s1ر#;]}Y cl9(0iBF8nsc`6lr;MG`'MLFhÁ-XjMYV2c԰x7yW[u 4(gϞ!wҔ{N\T` MVX*bBO.Yc"lD.YdG% $$l8A*ڠmuR_V:օvWA%6q+EW*4~/vM׮]#ZOyY9?eT:5kBE[vOA8p\nE~{ώ;Bv[\vhsa@ٻ ׅl_ ~YoM͒x륗jђ~,7_5o*XXf<,8i6,TI1{8srNL$چDrG Ŋ+zF S ISu֣EM޵kW֛obD]'z3=CtOaghHvlDSAxW8JtcZ6||cO6QPw>f2;P׉J׻WtA힢% =*PEתF၇^-eK0d;¨}/;;Eѷo_x ]}σeTsrʨ+VlqJ,RʩEmQpGjrg1>c:j²΂ym`c6wڴi-z5/ͽI?o>?fL|,XJw'bq2nڊqSWr!^K.TUVEXp 99uQTUUqСXcn\G]H(P{ٙ iQ[fLgVGqq1s^ÿǏCpcE!ٱ_/Fws,wE3sL(С.9uI=ÆҽGwvKX/*++Y"AkN@O#1 zm6QپA<ޫ0xѢƀ QyM4BBr-r .F|zک`K.\ȪU4i[K)Hx[Q5^,L5Vj,cC6غhL# Xc=멠"lqw5 Γ;ܦOx r6!g 0Ydgg٧ŕI9xf1 npSӢغs+}\\ &̚?`6hQ(JDQ < %Y,:uD^~|I&JēODŀ;ˍx)HRUQ! >** $oynq@ =+VW91*XĂL+)8&O&sqQYY]S)P^^#ˮ#Ans#of፹ȢwOÍׂ sЪRuxo o*P d&*!k׮<EV7Lg11`) !ӇCS>Mƣ?Jц"F3Ջ/&b۹+M{'Jw2Ldee1lg3ÆKQO%%xh W*_oG p86V%,vlڸ =r544à"c]7ouu ޯ+Ǎr $wlLDq:S/ yUvXYmյ'e0JJőr]> JpNܾi,Hz%=!Wn<ܒӟcǎZ*`MΙ?ҶS Ohzs=NOw'$/8CjAFhm?ÄGTqzPw+Ї}KuE33gΜয়~]-8ކ3¬xiK1+?Ek8c4%f͜\8lZDmt;[܊2 ?/}/=:L'|{gxu5++?p:mIHa"LP*b2x7XSwL%777/bނyo Fqq1Æ <#~nL¦tN|:(JڵkOώ={ͯ[38EgU"(9X t8Lq29`{_{%4HK> _ vշ4P8=|3xeWEHژW3 "JGӳo(}(dGR/7cPuӖTb I "X6;~,G<}{ތhjT8pQFU O+q\aBDZt *%CO wr1 I9?w^ ;&m ׀6jU_-^T-n"1n8p\5)=܃Sx8kYY|Çnp͔ݻ~jjbk`V[t)((W_}c=[q|yM3NanFni`:/餆-s%NvNw#C,`<=Lnro0iwg7+ΩwgQ9cs)PYQ9L$z%DH8D[_sг7\;BڕN IDAT+PLj'XdS[&I7EᕀLJ@qSFb[  ؼm3s^{CE0@LM+(J~?>vBɏɔd]:N8xҦmFCbF# K(A`tڕA' ◟Iq)ա>`P@TW\,\! rQ WS pӸ;gnBSXX[ٻw/]q ^U[/1.N?tN>d/rBFZGkoCūz*Yx%qqiv4ЄA%,qb`Jම`OdT!R /gs'QPPdb߾}lݲ?8itp'qE2x`v%77rvɊ+O6jo- J2ȹB_]o'..}UCNiaKV8j^=V1cE jFa.BWu'$j3?`޽xƿ% #:_X3Ωߏ[O&M-jԨǦ1QyԑLrO?5=dTA "++5IO$,^-ˆsFmrՔ`Dž\`>Zi&{z%j9k&7xwLMp^nCAA(&<sAqy;8^/K[`i,MPAѨ4kL|5X~&jLbO" "ͥ/N?fN3;;5s=;gpg0m۶pUWqu{byOp;toC((<ʣf7WpEBjw 4|~$ У+FU-|k! ZC4V|߬ab(p=3aSSji`%[9A#z 2.D-!Gl勤ڦO㼯_k7k_^KSW婫CӑѾ{zIbg綝BvyOB:72zmZBAa.s*ls?T0<ϋpap'w+~Eoz3 b[~K3͌el>- 6̼O_+n^ȕW2,帲T!e,m Y`*,+RJwm E2Al^ʹ-\!mt&:-+&0`@.vl6͝ǧK?Hv2| ~iř9Vk +a#Yl9n&w68i;1a WLf7meABBAЪ6lyL֭]׮$| c [|."HGZjyxPw?~Hֳuc$#YZ]KϹ?F|לI OL#|w|th>l0Mwߍ΂7_w<|8.~omVO Ph]x\^~`p(K9C~ DFUXҳ(h չ\\\poO4Q@6dcC';;箌g@ 8c]sÆ ȱ_N|_}U?<jjj,,D3ndcÈllX1D3x2rA-+fNg}}=cǍ44tPă7x7yNRҗ,h`#ofi%X.85щv)8:Î{y &1y8}ɾ+ƎJ;x`{koqt˺22cq0f_Ose_Ǵ]j*+cv1"ڤ#b(?)VYX1'&v˗-NX9؁%V4‚Wv9i"O 0=:rcxS#P$VOVUVVrY٧t/쯼3VC M?@r Ab-& TG{@=ذ!r#7r13c {m*m>mŪHYEQXNW~W،YX9S჌#Q{d;J!Ո3?x*`4!#*XУÏ۬(x:x!!aD>YG>da 0LjAƪ r(ݴԷiA>S+@ =laHLhքP-'x''8H2J^<ް=N;E060,Vŋ[؆-[%+ `?ۨ,4hأ#:O&TW&d$'j{ }.vddWE,}-B0Ish+=(]Lԕ,tߑujh OʦngnìvL=ujWA G1,_4,cX2AL4*c} |6 1 Q n_$HsyZdžXcVlb#M䓍#VL8qp=`p`G ÎdSۤڱĬ,]%@ qL;m Zijjb)||>f3\xw֛ob/b6kl6']ˋ4iJ𭅜0x8GU]e0&‘WţŪױA$uf%GLwPZǗYX1b)Ք!#mA5AB",ld5&u@ AO`֭|/0`̸d6m"77s:}b۹Yl9{>O,{ͷxͷ[g/?vڴX1k@ {zy9r,xuwl޼*rrr2t֭53tPͲx6o s:y$uĪT\da*,Dx*@$*z].'bTؔ:+$8CLNH90cĈ Ճi9d^>f#d R@ Bqq1:{(Qe$Ο=z4cƎaΝe^ R(z)<)bU|]*,D)!,-*>"+V`꬏>t=K"ω ^}ʄfhNyDVqUx!d$VL4ăCC&& xRC'@ 1f:˅^kcS3|^|EN;4xUϫ責,<2#F+/Ig$NX㢮In^0M*!+D{X+ECKJ KEh! (u'4b ,iA"@g &<#0fdaEJtIsƢaƌ @ A }cC?̈́MiX՞1ПPj%`%AFrIj>YU{b=LIҬzPWl+"I_#Fvpv֏*efLF\ .<*zR*رPD.}'l (Z-Ət. @ 腞,dHBd.4"@*p$`Fq g !(\+ĔC)dNzl}~u*-p#e%f̚03<=ֈڄk9l|j|0ljv KNgݺ; ROqPI g8N9j^vq` P& C!9&\na+߱9:$+@ @c0 Gb4c n/pR$I)$D0ZIDĨ4xxJ;*uAJj[@Htp#)3b`&7yY~.TF2}TH`:]Q!,M)%З"r[lg[6@ @#1q.YE 9'z|Oh?=_ͭO"#O[5 d+ X}#ϱĄxhivar=g1\I{1$F2Aj W[Ac$,`A 8"rlѩX>TQOT{|@ ˑ-Q-قтƼd@C03374Fjz`0POUBIL$jY O^}&Ԣe*-[+q鱁&^e)Wr:#)\|͗=v~ `1_QMc{u|t.6!@ z8YN>F7PDXD(jBc(~;LHd#Wtrx^g;˨MOx D\E'j%r_,60vA~b?}l72!L$b:$`8h֙ ~+j%@ -&bn )@OHJa q-P VYm7P#!+lH($nH?AJ1)DBs!.,xBZ' F{]E?$Ejc nGnѰ=c[+`/g\aWlXX1#Yʘ3@ @ epyHEfؓ`Ib#Rm0/x0!= '.vICUQUeU)E3*>O)i+)RDH}|>R0X0u[;4bU!oZ ;!F @ ACN 8+&F9Ʒ*^J%P%FaP? A#yL z Ah GھU"WkoUlVtyXp΂!x'p/zPIF{`8a}uWȥ: ͭV6:*?@ @ t2ыiP&jĕTcdu;,^IQeA!]d;h"@>t@6:ca,fV%.g}ŧoA(6b)Z G%ŊeZ}j?u!}8LKJ~3c~(B%xFle/Oyav!8|8@5/'ʹt:::JR@ @г U6UZbj'1p:vveμ4(|E)m*:TK GBF/XEQ@-U*ȫض 3cz(jyz&47!>32.pP.e VL sY wKQ@ @pp \B PCslUvPtbډB9^lg v(laAj&`a*6vK4DxD*,^7]* (F^U&CĔH6CNPFR f6@ v,So'`Hda)u⦡ @ t!?L@3!=@v%ᕤkK^vtM| 2a%t䡣?u .R޴=lݟ,rGsXNaD>6"2/9У\qAZ0 8;,J @ z +N/%]Y.?\;)̍d0SJa!YCbU>ѦZ;ZWO)~=Q:;i͋jcŊ &qIsFV$`† ]7^h0L @ ѣC yLA&*dq9mϋ?6dLHc`> H947y?~dgN1@pQ  ,Ddҷ*U괜BD~0"p ˝!2&ЩU鶕4QFGo3%RE(#iz:6 hƣ1GFFz|Ǐ>A:rSFM$I&jLGVm;f\9 >B8a*趎ML<#<,\Iq>R6IZ?`3q0Èr^(x,j s)**⼇ IDATY((VE۲i֧,ٳ?A)**ܒs9$y%k xt9{Fsh@ FO2>:.0~5"5P.bDb |(Ӱ8TP?IXLԊߖrɘB4QaAj,L0ʇ.!d Al6ss9\Wf@b""m_s̙c䜺s(W,0,Q CFY29@^+ AWrȓy~ #h4rƩgP}}Ȳ$IȒ fJEEEEvv)U@r r)K)|w 4 ?pY:/묵WҪJG&>腀,i]uoU[j]υnYlr!YՌ'1)kY+#VtT+>@}Xv#YάJ]%cUj<)bc}R#fQޣxH~9z>SA*9nF.{ZVW**(Ii‰l.)J8{ y5F{G;wbI3v &eYt:HYYYz),*I|7x-¸1Yjخ]:t(?X Fߎ=q1ozϙg9O|B҄kP[[[oAPbLtߪLEc9BJMX@ĮNUK!ZIGXV #RX=[E27^ۚ++b+qh c8سyX)z]=o7jޅ֕4=Ee6+-9tXJ `C6JYӳNJXHzu+u2TW;GMoL.\%7VσuX/ܷ8'fxg`”0 B4<}c>NDZɓ9jQ׏l555l߾ukױj$0LZ`` gDʤi(Xj쿟?=;oSwÍ7p}r?E]]]BfxfW3nj?'yʫ5뮞y5˗-gϧƔ׏~=08#zZ]9uȐ!<'yXj5@ ſIv)S1eU3d%[]LΝ;+)SƕW]ƍz\x,ZYyLMSNfϞ=ܹ9/iSVVW^];jU|I/4.m[~$L?ŇGXi!qi]VLFǼOTsEJ̅p&<Ɓ1W1X_r1+1P V^hD TE{BBQ***!L 8T0 , 8Ӊ$I_jU?Oџ+>JAa*+*Ub<`3wIp&K9ӂc4ϵo*N:^3||[n~,žF撢Kxml6sw/z?~˗e˖ansqq90sLylK?K׋*]$&,bt:$I"}'2ٗ&s;[&m d[ 1O)֬^cW.njicQ__pvmm-{%_^85Akp8ؾm;f` p? eY3 ýU_J{Fbt mGͺiu9&=w62+ A.bU*ȑcG3`p Y9(BC]=w [0x X^q"f$dsOHLc O%VW !r0r 'K#hF L(]P ii)GUU*1+1.K FN[|555_,;I'%,<5oz1[{I4 Ϙdog浍F>˺>ߢ9>K^AU)q9qӉI XPCAz+W&_!UX{=ˤ'͑GٳٰaC}}}<\s s-=7}j-|9 &̿ 76ٓe\3eV8tWYYY3 moθq}nU߄۷)Uʆ8zj /7ފqd+sܤ;g'h"u;//͛6Gn۶M_SYp+WTkƎLĨ=[]Ҹss/NÞEYY6n! M񤣩\#7=K1Ap8[wEGܥ[UQ1;2FC>PR#F]QKĶKrtm @r2<:k)**s#+UbAdegE#췢U.2&CӻOou n~ޠl6w8Uԋ3ό|Иa#+寰аPJd2_%A+93XR 95\LPp8x3f o&wa? ? xCiٻwo騮̬` `%3q .~)6nlX8?׳uYYYYZ|ϋ/Ȩѣ)i&u|nE]]k׮ _6l@mm-~Omm-۶oG_ߨǀD H#>c:7x^v)cƎI.UI?;Is+#8"#u/Q{mY1{VdsDY}iWҋO>&qJjqG VCyV&5>~X;B p:JB$F#N3#JΣEGjc.^l d2%*H|{\ӯq477XߘTdh߰ϙCdPF\xϿ|'e˹w2ר'x9Jˋ~ cᔔ٧mKnv.w'3 <x[ԢT:uh%Z?1[+VVZbR0$!뙿`>?;.k֮a՚U 0﹛%Kp$Oq =>i"PUa-ZI=~\p$$qQC"eAv &-^W,P h~F|$I^+C|~VF`,5LN'&s0+&2y o%Iw~dV_WO~A>!! @aivTL_ϔPM?I?(¿/-^NJtSqYefR9ddՔc2}-_KmߟO+[-7ݒоӧδӦ1,]4fż'7^O8>A4 GnݺIM⤟ĠAbc?n7޻iy玝:Uͨ/ӦƨQ())aΝ1Oi?L>q}u> r'pI'#IoJ\ '.O9la 7Ϻáy~_ԶaJ'OuLtjMu-"MA%G/B)0y?a<}.K?Y`0pI'K/NGzyW+T-*e0 E/ŧ&Z+iO>菣AcclgcJHo j "L<? МqpGFAx[Q?V\ŻrI% W9G޵Jb0o<\.gy*\F|M9kf̘g׮]8 q8j_{,:&{w_~{B}QnV]r;NƺK<ёCر;vh֕ܳϩ7lߵkvJy\ǓrQ?8"{^OZ_VV ϿYj!{4HY}y[< ={X=Φ6ՇT=fd[Ɲ7.ZF/, r= F`GUѫxrN}TJZb#)@96ed@ +5F]#7yس>ZǑV/I5hEX Դ"ˎ+BC}CgH4|mhhy$7;xy^~@s>czZ0"@dw4VH\NVSbs#Z T2l=`ilUTYXt:fɄ^Gף0 z|>nÁLd.`)+X ""*V"I)#m҉ |rC|{[=j;Y'S<{{vᘉhK RԻQ壀y8p@A>>})~l-9uLSu@ y_bR~?]w=Q.~iOG fdd$5ߴw誶v`x>")vdz{n<ƍoWwtpu͟v7$`ɲ+^Ɉ-LW>)Ik/Q5Uď1(e5T•S0 S[[˂5 dY&ڼy3ꫯL@ tKqQW bU|]k< E(Tzrzyy駟X2|>:O8'$o{I[nC$^rh*1U=DzU4qϗA#h3hoTXJȨ]2K%M\i3 26}dBef3}`0`XlF|>V,dYبd8p >EQ/>Auu5ո\.>s@Нn)X)k.E"YES @rrs VVT2h 0u>ŗ-Qb*ќu#ȫd`mZjrjMفttM|w| n)S5k0|msrdO IDAT#@ iXC~E"gRV%B&Lk龎U(DO^H{fӧO>Ä>>]nV$IoQHPʠ%Y{4[ JkUX^TѢ'f â=⵾֥ <=Hdb\qzOt466m6[VX-[hjjGh4һwoF)S8q"Æ fx<԰n:,Y'|ۻ`܄q]=AkW!d"(1 XUUjbb '1nKԍVč,>z#^.{Q'Gs|ogyp ocb fbs1ӇGyS߳uV>l!^ C=A"oܽl&Ūk1;.ɺP @.&QGh}vkG|Eᖛna,xIYdOGHY&QSMk;V*'FUJƊ O W+2(\.֯_σ>#fU6n . ܬvv;YYYaXUt:^={rZWÞիWc4U7]sGEQvc$IX͈Fm_duq2nkYcvX, UO5nHgҜJڮAR \XuP:֭[ղロĉ;v,m=wqI&1bC)kb0:l(oAw#,^y4"g* Fy XEQF吏F62:$ :XvzHpt,E|i~K]ŬLl6Çgԩ|xصkk֬aÆ zx@χUW4LFfj^r\Q<s-`X!xԛqp! Ni Y1CD( I^)7@K|nQuzmmM&Sq@jhaAۑe} tu<𑸪PW⋌1j^̃8˾}bn4=Xl6#ĪDA —E"_vz h|$h1+6 CKyy9VBsG~a7 Injkkq8477th4RTTD)((b`20`'Nd8vɁT_-pr,\A0jrBN ~|zu;xN%\ķ GDrN_G+y-^u<H/+&$A444Ĕ=䓜xz8Z{1FNc׮]l޼Y3Lq{eWtw@pa@BO:]Z*L F@ ?yJ!l{=+2F$|(-]KǤv5z$-:*6*1*V $)N%fEiE:\nz nkϧO>x^)..m۶?{n***hhha00 Fv;GqǏgȐ!U `ŊYM6Q[[l#`/1*uVv&Ǒ34iPSSyQ9[ef)tM3zƒh4,wz1k9Oyީyw~MӨ4{-3?XH3 ;{>3~V믿8u(l߾/p4jZ 2%%sUW1x`t:|̟?E端r/fG$UvL`wUUU.o`^4 u~l6[nn۶ݻv 4Dv&bU]hd/B 3 }{57lx~6Xy ?yh gtȨ\5ჲBS! Cq= `$ NСCQTX,*++%??`X(..^OQ9r$ӦM#55Yٷo ^Z0!ynTUU%~s{n#гgOw]߾}ˬYή !lk•]ʮ3EwĩtuM:+̀, *=SVVFYYzmAXmn=`?0`rMv+Y,TAotLM׊_i|w}/駟O,NjUjzy0y>nkO?䮻櫯"++,~ᇠm&+'r0cҳcO쳜<~2d(;GBt$66'OJ 7o`_}~;߱۶e/ʢ+ W9R'p`Z{7lmՋz=޻ս@pjY.N.{UbU0(Ihuw kQ*oqtCܵ˲LDD$*`ɲ\zg ϽO?LfZI_<);Yxnʿo`0_VۅW!иqSt㦦TQ|koG(hP@E*hņU.|\kM6dIJDTUfg"LəPܑp>pb*7qрPn/pằu$InG;+&"4EСCT5SpVebJ: |] wfzޅ W;l)rǓ?ɀx1L|'~6ӦMc"vڱd.^8]D4D:s jFE jkۊ# 3V6qiHKjJǔ#јH%bj$IBe5ɔQOΜ>b!Cغe_СC8q(޸ƺzϙ=cѽ̋ p8c'bV("uգJ;[(&OW\Arr2ddd駟RRRRGJh4rE1|p:t@EEǎcɒ%̟?bKhر#7|3#G]vgΜqss;ID cJ>Ϧ*hD y5%lQ5Rm"K 6F(be( f<СΝ;ӵkא[Vؿ?ݻwncl6lV!ٌ̕`nhHHHp Ъ%Guu5`Z5Y,X,v{NL?:Rx_>Onޙg8s ӹsg1F1 T**++6WsΜ9CAx}`pV6/Y=熲M'7\/?3?Hn k/M[/eee\uU/rWpwpzR;w.̾s6}Y@дPޙLن<qbU^_~'S..*}{5j?d!;XYiY؛wЗVG^=pRAH wh |dffh"^z< :m[qw&s = )\+oU`RG}ڳ`ǎzTtF+<;|Wu[s>U!2T)NNRH!IC>8* yYBާ2fθKF^Bbcl+>3q;(TD=b>rxcop(Tt@ڙJG@|Re13}l䪡f Tw v]m <7|~aZٺu+ , ==Y^Wbbb@xr_JXxU#I$ȡY(^EGG3` ̙3֭8p7MhsݛѣG3{l &\N>͡C/ٳg&ScHbb"Ջ[oDJKK9z(֭[,^ _WQ;.!FYIYƂ3Pi~0#y ?̭fEǎ}lz_<8lŤIoh!Ԃ֍j V[X*X]*4!Jb&†x-j$b q5UhX = (!S=lFJlM!^i<'j{W[uu4OJoJ `(//g׮]tFCbb"]t`ժUl6Ν#??,dYfر\tEDGGhX,ڵZ :ucǎ1}t:uD߾}INNDH@ pvwkBQAQF&c]U=`Eu M^^U^˫t:.:uhtTXX'9qyS ͯڵk:o@ f'4 J(Q%4^6zI$>رV'A LF^ lGDƑȽ#l +l<iCûм A*J܎δ`F (x MML䖤j[8$"TSRt֍Ç-En3c o+¶mB A褐B~3^"D(69ΑBJ`>l'AuePƉU\Ht X"؎h?f^F_ΰaxgn 11˗s1ڵk?ξ}e3f,b_ǎsΌ1ұcG4 呑 g͚e]$yS,SZZJff&+W$//Yf1dڵkp?;%%%ӧOڵ+'Ok׮DEERj6l`Ϟ=l6vѣIHHO>+Yojj*r ٬\s5 VT+6Ic/@ h.Qy$bU`U+XէWBXn᪭j6_,4K}s^个Qd@UCo==/Buu,""{m%\¸qXlC gϞ߿jy:tf URRhDesΘf;(TWWAZZ\s #Fjbِ$ EQׯǏgȑ̛7<.b&MDvv6*B.]4h;tؑի%%%b6)..&>>AѿVZEQQǏg̙tkZ2n8ϟիܹ3'NdСF>c eǑo׮Flܸ1GS 4*DPm`^ @\x0[՘rߺ + BjNa pV KN\H,M^ӑC ]eM-fX,N>Mzz:III5QFyf%::^zqW?}v/aEAA(w}kcȲfCQJ&L@NN֭Yj$''3|pnf<ȲeˈM6xҸ馛Xh 77^OϞ=ӧYYYZ| l=ۣh߿?W_}5Æ cŊ:tjdYh4ҫW/&L{'!!ݻsM7QQQ%KnV1zT*U=@В&XbiO{J)e 9A2F:؋>!*"a_GԞogު{֙VAxz^AxBsvpi|»JP4`AVXi,gvO|ŬC ͹s8x jt:NV322شixeX(--EQ`ҤIqq+Wȑ#>|8EEEl۶ͫHƍ믿W_͎;e͚5Y^qIZzҥK),,`Ĉ̜9L~N< @=5jgƍK"11g?AQYYIUUgϞٳ̙3ҥK9sL@ hRNv2Яlzg'?e3 D3f7G9Z籯Aq08@V5וcj̙3 ,#=z 77ž={xߏV믿n~"1QQ3?G KAmrYr^q5WK]tዯѣ;|DGGsy_:|+**8|0K,@Ɓ6^w-z /)ܴP YҜuۃHP7.Zr B=`\UUS_#p&u߰ڻ`k6$ nرcTUUl6OfΜ9LM9,b_Bl~2{+/b osms=f4nj?׍\s5$$$uVxյkWf83\~\# YYY^us M!DB‘*х"V5.xq2H8Dd4da ɋHP#|=zυ yjM aZrR 7)Pj 1 ^X\V:0ez***Xj;j%33< $IǏg'm>}FnѣǏWtЁK.cǎyyBk׎~1l0Z-111tؑ"yN>0|]2l0ŋ*p$;v,zSN%9r7|ٳg3m4NttRI%pwCpǾi߾=i#=A퓓WL (^iL8E.3AGll,EEE퓨% Tڐ(Ŏ У")`YhZp ~P3 y&O~i}kW E۱=QUxW[cǎn{y RSS6l۷g߿ʚ4 _0LTTTr!222;v,=WYsΌ7D>#떔p:Z&))I&ѳgOڷo*۶m㢋.b呙IUU`4۷/ӦM#33 bcc֯f۶mDFFrM71qDZ-۷$@ &Mu2ƙ?rـ/Zֽ9yj۳PF6؉CBCjO1o,~F"rݎ^'999l6~u LՒJii)tYeVtm/h8s^9PP<zY!WIh+3{ ;jZMaa!K,?'??䈈k׮̞=+ YINN&''}/,__Ljj*UUUH$IDGG?5k(..v[VVƍ˱r-0c Xv-EEE~ϊ+h߾=ӧOgر#""ܟO> :ԯJwhK/H@ MS/^Ғ$=ßmdd$se?VZ FUv!zɰ!؟U7zho rl6zj^rrrضs3p?T;[x'{7R\\̠ߡC/{A@S[vY8s^ylNcO4ѣGټy3qqq~&llBzzWޱcܳhsbҥ[Ϋ} qFo~èQԩjjrrrؼy3 ,YSRRⷍ5k`۹뮻:t(vrWر'N`6f=(L&JKKYr%ΝogϞ $Id2q1V\Ɋ+CgPtQQQu0LTUUwހ @ 4-S.-[t :﹛WuOntw}߇Zf⤉KAAv;;wDRq JQqss?>ۯ&k\ʟxw_ ؐ)XZ$ђUAsd[  ]lݲ={ӇiWOO>7WYEpVx'VKUUo6ov}뭷zsNvNe222X?Ϡu,X QagEQL@ h.CnXb'O~D[z!1x\^$I7x~6o̘c6A hJ$ <׹h#"x幵z6*P(F4脖"z$HXQ۷P`ႅ}n&999h軠m |xQ9{)^k}lEK! =q%7 a+@ +d`劕|e;qD|\xo5*&ݖ@Is";G Kg|E@1T(u#`6ف|lQD$i\Dy vwp8ԼL4ؾ.~u۷ziܻ'8h<\\B{^9j!ZYM$ NGJJ dРAՋDZm3fJ4LdeeqA2339vyyyiZ#v***aAQwP ܸīrm݆jcAL45<À+{|ʕ|TݻwW^=zYܗ[rp+WFo~o:UlÆB>6:D4@]?owmۯOު QG!2y\j%hh UY>Vާ[5\9JKK9~8f'NrHnΝ;ǹs0L"z-TWWT*UHɲbncXx/@ V &- 7=z)"""xH_N^jU<+yUSWb=yew8JKJ߰`Rzytz^A=ђŪ@kYQs XQ耚6] ; ՍZcު2w}eI0`njϿp'lw!2sWYBB/ϭm6[N:M- FwK兟 JAA!f(hÇ9*++CiZ"##Ǖ@ +FrO2rJwyNN-´ѳgOT*ܶol6|;ݰg޽;n,nU<4;6@7װhr~Isw}BVĪZ5XPNG4ĠvJvhUy=bmj, L2ٽܱcGs+УG/*lC %..='C lȥՒ|/!WVKX!D %jU^uomnT*< p=_~%n*6n)0q&OF##4 ak[ K ?铯5+x'ڽW}pQ)p!xWG D52Y;6(`IxW 7ʗ5Чg6\={K~d%#oxɧ8qjF#'M7_nlp~Au] VWV yCĬ;v`|YSEU0 H$:ԃ IDAT(c{VQ1>f(m׺ ^5 gt4 fO?w:f$'/[n%;3$~^3Æ kоn۬/5N '8rWmټi3F;O+Wd0luSMr˫q'ߵs_uGq-sݤ,^7cַ-]%2N+y[O-#*THT?ˠ[~ktRVZ/gE#ƛ\62&MĶGN8|I>4*)&9+y/|gQpq͈P7ᛡwߦrK #<2,*k^mwf@焯; JZ5eee 6]=OԹ`q\s-5xf3 ***ؼy3M $Iu9r$[6oiPQC||<Æ;\_+BUt2g٭\YrUϿ8|䙓JٳgE.NA92"3}ʕ: @u9`]u4.6VmvN  1eN!y q'-W/< gexÍ,ZoVg~ƏK~ h_VVM'Si7f+^x#z0b sO^Uh"CCqyW*Pw uůۺk3`[Ê5h) L( +V`Sqɏ4&[ 䶹a <ԓh4222yֽk~YÆ xVy;n,'O7`6k]? IK~ G}ga玝[hDGG3Xj[ne䨑߷y㯯;0g~Ikcs/x˭0'F>a9s[L&-橧bEI$V"}M:={̙3dp omT*nK\e_o4t:ZqƱamݻ5mV+>(|˖. h( }'OFV騮frAmaaaHB233Yh1֮ hn6y*2Wcra䒟sO<(1u'Vքx` X.%A˻m @S6=Ӵ3'n1+`tѡ 1uf} Ebwxhjkb-W;wnfK.p]uerǓH֬YG0[6o[nF~'JKKP69}~  mGq'ܽ{72 2wkjcǎǎ8qn_bAբjZy\vl;ơC8y$ :DvMaa!S;gDGGS]]Ν;6qCVkoNE t|gʦNW'ڟ}}YX|t}YpBmϻ*TrN$--QPFCnUAspA~VRobBO}4wy+cĉكL.yI6$  =j$T8&]pyP GB.HD8iZF,uŪ@:ϖ84NϤ)`šF‘~wBU\/Һu*kjIE{tѠ8=lKqo?R(u+P؜j>?='=U\s5p>׳gOL&QQuqFv111t:fyN<p'N}`Gt:vl6rJx#ho 6c/[Κk8x | K/}ӧ9t:N_O^ h{Hcɓ'ûcRؾs;nő#Gؾs;GnQԈ%7Q Y~6CX ǻ5u&dJSǯ -:$ 烮KhjH]p]xټѨHpuoFTtrB9i\@ (OעXF 瞆I \(jCu0mX$Y X]dB |! N}l;v V5 XD*1cN!i'\xjo;_/nB ko/f݁ yڡ†ƒlkb ň==Q>]cxy7|?UիխA6h#c#Y}0aټgc8q|ahc²2 0HBbZ[kA[j-VugD?2"3Ȭ̪̪ߧOuf{7###n|]վQnx˦{omdBO}* llDv+"&!687D'=T nT;GNqD&YSb(HiJsBRn׺ Z̶=|Poymod{=5P(y=i*|9žFP;r& ~AOq~fj;jP#"J/])60ͩ{Ժ!Ŭfى^!Klb/1l#M\0q4w|3o#YYrA,(>۹CO똘(r.c$A3278ULѩP ӱ7;e&J+պZޫ*ĘB 3o)% 3MYc8j:{M:1}6N şdnmϫWr> WabUÞ }Hp>)HsH+ Y /[K NX7SL0tW5nW::b9@M]糉*I>k#ss;iIPr~1$w}c[ObT`+fyyuIH2C, AAZCῲIR%\E{{Dq Z)uG+br Ɖq)FQ5>U8,o xB($8Ū`ѪXs:Ū6"vbi,Vzhly%d9L}`wϱHq* "V BXSIЏN9:,yz{Բa]E,u0J)asD/WsysM™"rF[޹N107) O S9SWzm90'`8Hr)~I^r2fĹ/R$89˜~)Xe۬rU,V=@uiXXn :ΑTHogIb$ Y aCD b MÖЙ4v 2O0AXoxEu.f:_)@7ͤ1Zv%M\ q[Po+<)l%f$_VX;˗d4˗e 0q2,%'A9è|dQF[yܞVͽL=GWX.`0x*hlFdD9Ѹ11g|c7}wV5ƉCT8Nުf[얫bK$x3Y i^ HT qҨI7)=a`+'&~?\!u~^dZrIq.LKVQobJjbU w'_V3e`4yAAhג" нbVC `AbTX(aвS+ae&ИDw %NGOy.'SxC<@aBT{bUclo.Rz:FйeN*Iw0ŤXh 3(|)'E/11Wa1╗^YɗUKz>%9ѲWL"b|'YpWW%RE̊.B6jծ|Y{`r#/8  Ἂ$/Ql帊"f9`EZW6%F+[g\0{ISrv&X筲B-RǤ*(0X/e"%L^E]Ĺ,!2̣$x#|ID$*Q䓜! "[ Bxu JGAڵv^LϒWr<-9ׅ8<0GX $Mºpu%\)4)m5b{\kw:7{1]}in'} 8mcൡ e8*qKAH(G؄ LS%o4ixL{EóLϺAEȑO`6]e.!˹$,3\F|Wr^ELne;Y 󇌲?e?e+ e̐I4*,.Hkfc%+mJnReI[j%6VNQB;bUAXx&DqNG)P(3)o,jyeyŬND^YbI  9]$P'3h7qbaǙ37XQJS,% XrXf ZxWuꍕt DZ1 Amr__~s`NAf Ch$1FL$)+t+.Ib([C2:i4Dg IDATS4H=LνSF:NJmOA!6bQ"7ޜ `zxP |lпI*Ya6^VA'DӔJa,H Ѧ5 1 Zm!μWKTB,k0qH5o9C37ZYx`m=)Rߤ F#ROd^U#6#Vvu9*0m! v D*Sf$8N~ B~H0By뵘+,oϗe:^I~"&G阰{n6tnE#FgƂ aTjI ו4~ Lj\)ClE8U"`E~kWle*/"V 08x!KTH#iNՑ$I-or?&pmeU10ҬhZ46\RBvYp3iNA88z]:TX]ϵBmŭh"P``R <"C[%0g+4%*$P?/l%M i WAnXjkߦIzPZEF6ed~@&,I*Edtlצ矿}cO&TH9EWPɶڃηQaյ>Z]5Etv4r5DOvȠXY 1t%X3v"Ǐ ^&(c"f #B 2:'SnѻԨY $Ĭ kV2dcRK_vn 5o Ę$Ս-0 YҊ8bUYum)`x_0;W31i*;ߥSZvDac a+D&`m!MW3jy|(30ap//uݵm:Y[àVe%՚ R~JwY +mv̵ʃel5Th`r':uV@x±^c6s((,Tp|ϨԒQ"^筪ylA6"^u ')4Ibl.WV9`8 :&#φJ|Y ݵ=*OM.qIZI J)’ۄ^_"^u'qHAcrϼ&2h'a1DۨbV xj'0qT]}VgN<؆1PP0N6VLW5 6DT*E&,\9r.oVJ+/nkqRol w՛} (b B_f)'iX3uRCa80I1tLNS WA"eol׬,$I%i1A.\; *h}eC;IM iy9N195YViLNMvGBR(b WLX,FPo6^IIm]S2@ z;cT6A=@~g7rseuw)ssVv$1H,H&fEG#ʰ5{DC,%#=]N!kŬvD[߾ 0E,&&9*VVb.LQf`p1+NibRM$ c WtT*G#-g2>uo5M[+]gpsV_|Ʈ3֯'4ML$pu|>V`H$@*wULNQ$ g@+AVK/︝Fv޽i%I.2jZvERk\bKTXqI2D!#DǤARXB;+N =她QϗebR Jتxm$TH$f WUt`5Vmfe@Z,ښ Bq WB-T4Nqs8ܡNOOo|-7266{{عk'9'F_ BqE%h?*'??2<t\=?g [:AcYL Z24C!\zx=LHAA 3y3m"ִqmm_Vҟ0}HT6[•t}鸷w%%a=w!8䗿%ˑW.|9A~~~dA0 *sss|d߾}ۿ_diiG}7'NpM7#p`MPg>Ǚg oxêՠX,6q4MpQϪQ5ɴ5k(1&6pH5srtջJ<A=\T*vnr08ǽ?J4ou]տ5w\.f@TzW^,PfZP[ U5/ k;2 gV/JdڪFQ ߖxu/+hvU b(hd,κ't+veRV;/344{~_˞y7033~v]tWp%P.ٿ?{ fϞ=fx1 e|^YiL,MwUL[IljaP [BGaӭص+]%b =?k}d+`|ys]w?׾ZSj\?.Һ@e5Lrw^. ͳh%t*T:J`d2Ae Wz+z+\sM#mVկ~O}Sݻ/| iZ%pE /X]2 |'`9fjTѣ[bUPF}M5D3,TA-yzO\.Cqwpȑ#k7[A5$b 5B}k: '*N˲l=unuW n@QRώ;x{˯~+^~e9ۙl̮xO||sczz`jjj?ja \OzEjFv[X! B0[GG+_ _W"#îuʥ2?{ZXKz]^t7$"nBmV4)+"e+H>x1cQT'q%?IЮOX'NT*qg'8OZwVe= WNJwy'7|3_~9 _W\qE=? }׿׿u&''7?i/rU?jY9 L_y` MPB`ouAaNrx/Z㜷<{''R*X\\\.H&֝w\]րAJLg2'JӟYt/V؛4=ZNnb$FNBt*+H$PUUUI$?O}ޏ{r/79oyoG?Qn_6p*fy< ###y~p5װgϞP;ykxӛބi,..UV;•}:}mBy`1T ]$Ѻ qI&TUrrﻟ>~^[}{}!\A%lJ&\ve\}\wuܹ}7]{&n; g6׬UJ-$p*lhn؃ W<%+O[0D9b"O]P)9!h|Fo͊ԗؼy3ze>rngyyW9^Tj3{&:ΓAx? r r rÇ|nGȑ#Wn[%k)4º [\_x=Dap;oߍx嗹wY߈Ta(vyii=v؉ַśȃ<سm Kn&CS(n:eY4p+Mi<쯝bQr"~(xłӫunTUk捓A%B &*&%믈I#w`]W￘u7ݻw377=Cu&`c 7-a _q9??C n0UU1uQzCY$&& d2Հy'yZhKc"Hwߘd~n~łi13=ӥ4 C 6Za`d\PX"^uξ}8r0MC:z]N{5u̙( {E+A/B۰p%DB ibu?;ɽiy\ W:F7@1J8`jfA4nMju ,*Hy~_BX900*LYĨBqIN.#ɸG4MA$ Lfz'!J"Ы|YE$,w~}`u1&,e*AT)w\4Cg*, I/ qd_b+XA.,xG!N knox{=jDa'q;D ^+~[RJ$ gU0Ƞ2f rأ 2 PVѺʈ\'I)R((䩲L%>o _#wn^3RgBĚBVMqM̀K1xav1Z1@C8}g{ur~:wr^İJh 5C @-m Bo+ +"^@u WڿA iǞMպ?Y^D"~ kqs W*&X cԼMFV>kNJ;'m[S:1,[ܧs2KA&r#];l5 ,Zx) $iT T}I(تif֛hԘҠYB#&•e97QVramCcF8x=xs%mak&Sh%?\GC /A6"^ Bi%ɠEcqΡCa3ib(ЙLcyoln7IaQqYG:sH 끭롂U1]|^)``#IŬ:  &ٿbUv۬Y]-h$0JaFR-*n WN ?A٪]t1keϥeAa5=C-a VޣвhbU*qLf=zwSŪNW%*l"1QBe %U֩!T&Cgљ A!:"^ BLdFpU+' YrٙUr]|YpC k7OTTA"Gt+Π!`QATg@:fbU=o*g?W=8mF80*cGTy 5?Pғ) :&yd3DJX>%\8+ o$fami-B +$A^Gas'nPN׶渄A$*8E% uUƝ-*lαF \a*p*(cBBo rUO;We ~Jc+&nL !q^M`awN7l#Ǹ %yKrzA9}lǾqh떰̺d\"6pl)5 PheҰ)1r`/c"^ Y4v69+1M&Σ]A*(h\1A9xӖgJ9W%Tg0@dPhclNm߶q:&W2̇ل%֊qQ"Wj~;J*WyFQŪ ިUͨ`,%]'AE+Ahwf"Kt18ElMlN,+a> 0 |Wзƍ7$伙E ]fKT6N1nmxCEWWTaQ9qaEwFIp5$2ULJ蔩b`Jk㨴U5[lANpGXXD0*pO(j,(u` *[2C_~&6 6Q,hmt^K 'QX.HB 6ĪfWhb/VmGr9VGA"" *_x֖ >9@0:1>Ed$|(Z9.7 rpY7#'탞=[m}=*g~ &e,q~˾^ A9?g{CCG/W˰6#Lj5AF<t3̛2C]ky=S|97yٝ{!h{:Mt:!nFR{(^YξDs[*&tLr{#@*`9^V_Y0B1b@e8>3yRɒf Ytid:ڰuZܡ`'F#LPG,*{IPA\2_qDrJC^vʯ̖|mBA+A!+20y%.c+0893_)C^^Vk9~}|$!:Yeaqo[iϦs xYnQ|Ws={HH䙣h2g̓(Uqco#dM< i,׽=Lo 2Ѽ^2Q_*Ty`]J#TxJԯ^^O1ij!KV]nmM0 KHtbkJATDU]!kLg3ί!߃úE+فɷxU9zy41Cw}PaM᪙ia>Y#$Txӡk kc\19Ac([nE'F{11P'0̒pN9(E Fwe9Uf!x?Ks1 4b\&3NzvǜK, "SXRJ4H 1kZut/ˉck%"Us1ݾ|Y[ɲ,s#2 !_3E9).qAU=ZStӱݨbVC<+3B3,Q/1~MLj&.Oުm:#rgSud8䧖F&_1M]>Uv?EA2Y>dImQ182GXE9J:y*T%j! n9a&H.P>qGYh休5*4( ٌ/+8Љ7V.?mce;%\uLGf;D.Y4>(%t~1n|ߍ;hYY5oW;~lm%I2<í절-:1F@4dg;<mZy(8߂Mlf?3Fٽ!>$)1; XPZXuJAz>Bf!ʴIENDB`BShapr-0.13/inc/000077500000000000000000000000001405711530000133615ustar00rootroot00000000000000BShapr-0.13/inc/Amplify.png000066400000000000000000000035001405711530000154660ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDATx]H_?Z:ژVBzQ^H]hEيHgdTV :˚Vi&P2/"M("/B1H[/7Z6g>p߳9nf-ӝet'0ۑT*^~`p)C.s-|}}"==ݥ")DRYfW\rY:-ZdWl*qHk.gf p,;nn'O{n%Gp #صe2ZvdQ*\p&>44D^^s2fv =jRQg4g3DGG?y_r?Q(ݻ*9#`&qBu fdڵsB9a?9!.xldN][S "33BV^F;P?~&Htt4qqq؆ Xl===b4۷( Ynݘmݺ???|||l6یC__:XJKKm'%%Çg` '++S^^L&#!!l*++iV`0 --^"##Ϸŋ6c(J:;;QՓT'TUUJ^^7osѣI5p`RRRX, Z-r7nIDDϟl6= VKxx82l9F#f//۷g-Ʃٳ\t'OR[[KEE5L`m6bbb>yyxݻ˗/ חOdRSS)**Bjjj(..fӦM<{:OXXx{{sN^zƍ'}QǎYjׯ_GNzĘK$n߾^Ӭ_>X뛛IJJBdBVƧO{.Z'NŚ5kݻwjU*)))σ|mmmDEEd,rrr8t/_d231jhꢮ:6߿'--t]Fdd$/^Z^Jcc#---466c3NAAc&tRs0<<Ǐ'w#y^oW}(>~(F*>>>Ϣ_Ac """STB!/_.Z[[EvvDlaaaB!V\9xBe˖ rQVV&E}}(++r|~uVVD"A*288L7CCCH$+ E,\T*d21888RD2".@׳xbX,rz{{X,{w߅DYD,? J*fǷIENDB`BShapr-0.13/inc/Amplilfy.svg000066400000000000000000000214741405711530000156670ustar00rootroot00000000000000 image/svg+xml + AMPLIFY BShapr-0.13/inc/Balance.png000066400000000000000000000031511405711530000154140ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDATx_HSoǿ157gQJE׺P!)/ "Xi?q`aI]Ĉ.vY64@+nC62]Y<Ӆt~wOSs<}ys@`ȖN10eX0eX0eX0eX0eX0eX0er-xI8V2*Xף3ό{ѱ6Fl۶M䫮ix!FFFMZ`^~dgg'wMYdfdJZ[bʑlX,=X$-p{{;<lˆA{{{qI߃GFF`2PQQ!V}͹N!&7Y o9xv9`$eNMױn:I_0N'~%ǘH^zE`0H3EC׋lʕhmm˱o>Q܊+Յ<ɓp:@gg'L&WXXV+vTUU -[֭[xx"fsԱvҥD688 566CE~~~Bl6-ѣNYV;v }}}8|0`u#++ {ؘ:sE N'|>rݔWTTD}ӧOƪLrR""X,dbιsN Bt)yvAHVh$K555RRRDݿ_`)|hTJϟ? qzz:RSS68F! yj|DiAՂyB!T*Q86Ȝ_S#IENDB`BShapr-0.13/inc/Balance.svg000066400000000000000000000173021405711530000154320ustar00rootroot00000000000000 image/svg+xml BALANCE BShapr-0.13/inc/Bitcrush.png000066400000000000000000000031451405711530000156550ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDATx]HSo?59,GReo,.άW.MQEE+I]A+bu76ڎt9v9`ACd4R&`ٰl)xx<)/8Rf_kKɨd8pJNNNLuaillNRWTTDWW;w&ҖG"~L4440<< @uu5݄BhիW?@8fƌRPPӧOcG"***EoJjjjضm%%%̝;7|tv q}1vL&So):vgώ n7nHڶ ݻwlݺ5Zގbaʕ|1k׮qII |w9SVVFGG^mxٲeFAb`f͚pfppŤ&BE &Y}zzzزe (J+ӧO2A$'Kǘnxƍ1ek֬v{nEcʔ)AYm/_ŋ̛7Mii):?J-۵k{aŊӧOtRI?7;r3^=8 ݸ$ 6p FFFu8رM&z^|̙3(..͛7~K,E߾}lJJJxBTT`DwwwL ܹѣGy?ҥKI[[ZZhii`ѢE<{zn޼ɋ/\zSN0 Z}qyv;1qG?W4L"999l޼9nF@0.z|0_|7Q3! ࠤNl6bBB5ԁ$5'uIENDB`BShapr-0.13/inc/Bitcrush.svg000066400000000000000000000146511405711530000156740ustar00rootroot00000000000000 image/svg+xml Bitcrush BShapr-0.13/inc/Decimate.png000066400000000000000000000027471405711530000156140ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<dIDATx_HTOǿW]ܴ?``B .$iX KDhKjE,Ao)>‚$PFZn n=U6<̙3gνgϙٹY6Οqxqxq"6ہ!l l8M f`f`f`R7Y7h<qM fCˇ<q!qx3Ϊ7Y.]Bnnn|~~o޼'O۷?gc Fk1;;KYYYsy m4jk{o>,,,={ԩSFWWL&~?UڵkD?& .=99 ˅zl6AaIIIk;v 'OjE meff󡸸Xu}}}c[u|Un(صkV+4 , WsQ{b޽{efC]]bcceYdd$ܹ͆ ;۷oGKK Ξ=sΩC)dl^uYwء:@DDd4(??r$"n2  7nzx(###Ncc#̐墹9 !˥vzn񀈠h$''Qd2X\\ ɾl ZZZp444@$TUU޽{8}4+yNZZ^roŋC~ݻwj*d#dl |HOO?(V~gϞnBQQ>|t"..fYއAÇvPHt:}j8NzC$Y˗/ FTT?> 8rQ__ÁqTTTȶRRRݍ .ygb ~߿_UmY_ 4::J'NudSSS^YYMOO|Rwޥ?400nz555ѧOh4>ˣGh~~vܹf˗7=zm6h "GJ {$IXZZhZ-^W|>AI?hzt:DEE|X^^V ?60?fd'IENDB`BShapr-0.13/inc/Decimate.svg000066400000000000000000000257401405711530000156250ustar00rootroot00000000000000 image/svg+xml Decimate BShapr-0.13/inc/Delay.png000066400000000000000000000042111405711530000151230ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDATx[mHS_Zsvs&\z!?(AmciQj}"(I;-Z:SiBJeͩ7~{syK?,YB;?8'b1@rr2HR0>>>|0>>..%r|2l6t\ . ҥKqFw $ܿ?Z,8Χq޽{OHQ9RT*`0]fqappbX ֭B@ӳ"VΝ;]]]aC!( 4McQQѬ4rU*G"J/^x)Le'abb"RYYY8g ]]]+W R)hllĔbM;uTml޼m66H$I ̲,O\㰸-[Ƴ!6%ޢ-gEիW$SSSxIR'''qzz׬Yt999855EڭBǒŋ{9- jj㼙\PP x/p]] hYYY@eC VDr,n\.dʇ:Ml \\σ;E@MM Tp@SS4 GYF$' ++ϟ?d@|ynJ ӴH$BN>;;ۣF- Gb zpҴ'a} ,JɥcbbW }v议yi5=x9Ttww[ XV>|^uiՊyyy?KXݎ~Ȋy_>t:@o{t(Sדϟ<^޸hW,˒?W]z{{g .@RRzp:~ VP[F"O# Axݐz#%E{}/ʕ+P(f059Vd2אxT 'brr#T(&mٲEpw1..Tqvvv"0i&4YRRh6D~~>^zk44͘96%%M&SP^˄t:#sׂ>DD#G`ZZ""|F#EEEvZDDܽ{OJ%NNN"q3bM&cRRH$ׇV/_`AsߪlhhZsNDDvX.{W`jb]]l6| y޽n+V`cc#ի4hUUUDࢢ"A|666QQQgΜXr\[[ _|NFm6vjP^^cccp!hmmgϞ ( HOO`0֯_DǏ/X h|*;T*!!!}/WZ7n܀rR;w>} v>} E}/.`Y,ザ Niii822}-BLCCCGxF+++<ʎ?k 9 fYF#L&;vZ&`yv̲LH$;::at CCCoZ  image/svg+xml Delay BShapr-0.13/inc/Distortion.png000066400000000000000000000054371405711530000162360ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org< IDATx[oLS~nK[,Vp"P26%?dnŰ0a"?(Ι݇11DtPJܠHPP` } XO|=9{{S1Y 0Ŝg9<1Yo`BRATBnf }}} gw9k+V@`ժUZF`` y͆FnȈ?LRdBB9::JESeRTRPH eZZO8.CEZ,8q^TQ,xF\\6mڄc޼y ?3.\ɄYg\ Cff&v؁P( v/,X h4DFF" * ~~~Xjz{{P(0<<<;R&qÆ 4 E$Ɯ._2C PR1''G N]]]\tO9cC͛qADFFBTJ---(//dBKK zzz0::CTغu+!(Çqܾ}[Srx}M 088Sv ٳ Lxrxx$-[6xHz}rӬ'IqϞ=ej5_uK͛7Rz_~=IVyyy^C&$\СC| #""<ʧ$l`V;XWWGc<ʨjfeeI8^J^Q>,,AJJKKnጨd%''cٲeo֣nG[[k׮ѣ0 f3j5DQJ?BBBzŒ\8YヒAա7oޜajp})DNl6x}` J-Z;v //ňjEmm-{=\x< v-wٳѣGoxxIIIx Ӄ+WرcX,Z^<\v OFTT=9rK,իsN%K;`͚5jp:~: PVV`DotYXX8py\ç~ >|;wkb…GII QWW&&]tAW_}娯0|Mڵ ֭ױvZTVVEORR.]+W vm6f\r`X+WQSSÁ BDEE8x ݋X`0 99VU裏AM;m0Dk4{^_tEdzz:SSSI˖-TyISO=Er?$$VU*W:NHQ}9r677߳PEX`[[W2fee%:ɄaLxwx+ORlFaamӧݮuvv/Dbb} twS$$$믿ܮ}G͕t[Vh4`0@ʙdoۋ8deeaR\.GFF6oތ dRvI ^N`l=쳸uFK(.mظq#PYY ^@5kO>n_|v[mPPP߿_GZ /RkNJBRR[4۷og}v$襗^B˗/Jw;yxtٳg]VmPP{1ݻϻ#233qPF(++ǥJW^… ~|ITUU!;;nODػw/jkkq%9sCCCdk|455ɵIN ŋ's1au7zzzXVVD;{9L&yL?۷t}/_.w$kƍ,,,tSѬ IfKK)\չtҽɹR4@&gg9fĉ!ϟlݺ6mBddǿ~444Fˬ3:'IENDB`BShapr-0.13/inc/Distortion.svg000066400000000000000000000223411405711530000162420ustar00rootroot00000000000000 image/svg+xml Distortion BShapr-0.13/inc/Doppler_Delay.svg000066400000000000000000000214731405711530000166340ustar00rootroot00000000000000 image/svg+xml Doppler Delay BShapr-0.13/inc/Doppler_delay.png000066400000000000000000000057611405711530000166630ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org< nIDATx[PTOօ`SBgҴa[h440mNRTJa-qBWJ~:?E`wy?f~3޽{9{}InbB*7qmq2P( Chh(j5 bgΜӧaXDڂS^^?~V+MJrssi Htfx#e'u+,KvZ3[$\Edd$q:ձ,?BB%KPHΩH}}=mۆk>k gtH*믿V+8pZ--ZhR9~~~jZPkFD΀DW`ʤT*j~;Faaaƍ)AǏL~~~^;XT˲,ر<<+ȫVfO͛7n=tIR1[6mȘQO?4o%gggnyH^cǎIddd8L;R~~>/ . j+D=ĉ:in:'T]9Нwɋ$w>z(gЂ)0 9gN;888KnV+Nli`0dl,K˖-Nu>D">|.]Rn T*?>i\N4oG\xGH$NI'1 ,sEIZvZ2 r233h4r7nMMM%}bi]AR(\(qŁM;j5u … Eלs]wmFk4^Eӧ9h5rFt}}=mٲ.]خ.vkEVUUxpG1L h4Rff&rer_|E劮,ZTrW^n={֩YYY Aaa!ܖ験[6N8]Li;vauwzN彽hllDyyBHOOdž ʯ^'O⫯B__M Ξ=K'OrLKk֬]OGφ^~<ضmDEEn4 Lł'O>l۷z+OjE[[>CF+X~=py֭/|PPO?K.jxW+Gii)\%""ܽ/Z-PYY0lӃ'|Ǿ}PUU岯1$&&")) :,B@/DYY{ũS&N&GDii)"""`0n: d2/pwxbLłOADD"##y >> 7._" fa>QQQ  11VRXz5y,^-=ye˖aŊ}HHV^;Bрa]BBBx:\r-yEEE~@NNJJJg>&"==uuui98..z& UUU0xPYYğ&^jn ` ׯGWWdz|rdggstNsK׊ TTTL8|-ZqLn %%%`Y}wߝ1<3CLL rJ`Yؽ{7QZZʓ1J%ye܌OIIllFii)>sDGGD///<Xf 4Ӡ'xCC]KR$'';v:]scT0 HJJG}$ݻyP(éS `tUCCC8x ::INj/̙3xQXXv>>.mGjPnn.I.T*fx{{a f|||0<<<)$ \,X^^^u򂇇`lB˲/\r\BVxy,: ϲ,f3T*gAV2 v27aZ!%cy=qüMQǕGIENDB`BShapr-0.13/inc/High_pass.png000066400000000000000000000034671405711530000160060ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDATxML[VXDt#R1V1D6ꠕ\ !nPӨj0Ņ"DbLBj4.\MZL`K[hV|yLW旜s{Νsϝw2Y<)Y,G p#8ˑPb;j*]+VvgF`Y. ,ˊ L<V~?h4\_$i&ngZK2:Ej} sE5PSS ߉^sBߪܿ?r͛7BFEQeY^WVV>xYY/###(;=gD)z>q!J.--y^!MJB4) 2s#i(B4E%t:A!LJ@ ǏRp3`2iy%fi_>}z6-eD sn˗ŋ)83}?rc²lt$!=bhXXV7f 3g΀Xl?Eqzo߾ő#GsNbI\Nkk+.\+7 D[[n߾D"d28ntvvݼy3=|^ƥKd444h4BToBfnFl0X Br^;4M4MFڛpԩS(((W: aP]]>|+((Çs~^߾}ttt`˖-E$K2k ///LRmoom4,]>AKK _łf;v/..JBuu5޽{ 6 gϞF^:l< Kd2Bغu+L&SNOO{…hjjsssE&) lܸ &enG<Gcc#|>nܸ7orۥ>p׮]ӧO^|{!aB0>>,-પ*^YJPE\0r9jjj~``0W>88a㒒j"tW^d$>|rl$?r9k.>|6 'ODQQr9WXXfY.)tuu$Z&gr W` R\\)n\w^N, )--%Jӗdɓ'FǓ} image/svg+xml High PASS BShapr-0.13/inc/High_pass_log.png000066400000000000000000000043731405711530000166440ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<xIDATx\[LS[J/HE BPDrPj,D %$MЈ Ę \D\Jpn?z_2I;k̬^Z{vy6XY-+pjƍ!`V ~~~ppp`޿ϒ |#qO Mx<ɡ_[eG,9"u[𫁖 pzz:-,,Pnn.7y{{Sll,j PH^^^499IϞ=P"b^h___l۶ 7oƚ5koFh̙3{..\`pp툎ݻ1==xheG&JZ&T(55j)##߿OO>%ƲgUD";wPSSpK|}}l>lٲd;b1g###1ҽ{X}-`Xϟ#99ْf@zz:"##|gggܾ}CCChhhJ'c -y<X:SZ RtOOuww3($$D"UWW0uwwSoo/ uN#V3>>JѼ>pT*mmmmhmm%\D>"+ &HR͡=/bbb;%%%L&'''e]Z Uy7 `8k~5D"}ಲ2k|Y#OOOk|UۤGx<6m?hX38**W*"ܹܱ1!NS;XFz.8 ppp0ƥI~ΥI~gG:󑳋 FGG0i p6Y,,mkyXi ցٚxzz"55d{YYpEΜ[ĦMpemz]]]ƛ7o8/_F1j l|>??OIIIV?XTU悈pssӧ!ɰn:Ǐq 8p)))8tC7JKKf|#p%_&Ũ`x(ヂahhhI߭*@]]]T^^ΒDD@7L&#"輼|Kdrl߾j* V9[Tgett4R)KR􏞽o诿wޙܯlΟEZ~= ѵkL(T*ڵk}ZH@&e&''իV"Gf`~~H$ "}bjxppp`}!J111ff:Τ? H$4[X H 055ا/bvvָETʸ.ԖO,z+k^SKIENDB`BShapr-0.13/inc/High_pass_log.svg000066400000000000000000000212031405711530000166460ustar00rootroot00000000000000 image/svg+xml log High PASS BShapr-0.13/inc/Level.png000066400000000000000000000021571405711530000151430ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDATxOH*Qƿp&@"E,Kr#1qYkѢ v-$R oxoƌ7ts99g>J P* ; l6,0 |>z= N8YE#悸 W\P+Hssb \P m@.FillLv$:?%1P?%1P܃Y$UH!0˲0 %FEm `fbG?b"V25uVQCd$J6JblP3S (ɋS$?hjGVQYG㑽Vɔ].P*@I1>>.g/ jDOOOcffF0`jj |wb,v;^/:;;X,pH&X__Ӳe088L&[8::(x `ssdB{{^FiW6YT %~. pTw:h4XYY)&%'$qѭp8E1ߏl6 qCQld*NRݕME" /i4@&2~?... x{utthQH"p2rLB!8lmmauu###H$H$"E[ =͒j///qss `~~X p;;;b>6usNNNzHߊ fVJ:;;#_HnwhRJ~* kJ1OVDuEG <.5fIENDB`BShapr-0.13/inc/Level.svg000066400000000000000000000234601405711530000151560ustar00rootroot00000000000000 image/svg+xml Level BShapr-0.13/inc/Low_pass.png000066400000000000000000000040511405711530000156560ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDATxZkHT]~fƼ6E^ RJH2*5T,b(R3 DQHRMwCM,JK,GH^25qFQ✳NtzoX0/}>{l MMY6g9lr尛e2vލyMg73^v|mmmBנ0LF4,PTTԴ4,3ZMNNNBkwo")) =TxaEEEg5cZ0Zttu1pttDHHكfsmP(Hs\\\ N௴xSuZZlOr"D"87S`4669""Bpnf&Z[[QQQ'$$?eEGGs2۷o$ N6GGG 876E6z=^x Swq|k֬ 6 ׮ƴ~ŋe ^[O144dJXp@&˗/L,ax Ē%K8|"44hnnFAAzzzfwq|nnn0ZuT*z>88HqpRToߪP( 5o޼۷T*sN$$$``` A\\zE[~~~É'P__KP>DpٳزeEwi3>>111X`')))XlV9,;w.Ϩ~,QYY7n>>>PըBMM 6l؀ @UUi/;;r11a(**Bmm-V^ͩ[BVsٶmiW`0555 ̄?n`pp/OJpN,""$:s b&GGGs bYUԉ):&&墳·j*l2Ltɓ'gIDD۷o܏w:::H& >E~訫ʕ+'](\~^^^ظqxmm-:::VCRHOOGnn.VR*y&bcc___sؿ?R"/_NCXX.^8yf<}j6F#sb|Z˲r_~Ž{0<< 4())$~9 7^QQA`0вeH&VrrrM6˲n:ޙb"9burssc4ߜatڵkTZZjNCܹseJKK?گ FZr9'&Iбc?V[nQNNNT2~[˗Ӯ]K+V Hd.FtyڷoŃ0 t`rrrٳg'""///EIWRVctT*^z .r)))˗W333qU0 BӰR---hii̩S,,Oڵk兀N9s 22 H$رc~`4<=JÃ`0cz޽K-hS"Pcc#~D"FIȚ2Lw^`bVdoߒ7IR1Dt2]d988@">F&7|F eu0 o{{{8;;cxxcccNNNj'9`YFR`0 Xirf9f3 ?>IENDB`BShapr-0.13/inc/Low_pass.svg000066400000000000000000000170441405711530000156770ustar00rootroot00000000000000 image/svg+xml Low PASS BShapr-0.13/inc/Low_pass_log.png000066400000000000000000000050161405711530000165210ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org< IDATx[iLSM~ڲT%F-Q$W *ܐ4ЈD1"1 `EEܢ *aЂz-,WO$zΙ3s 3w X0d: qXkjjeR7fBա3<<>>H$&uP( ÇNCmm- T*m<== WWWZ8qaOD"1y^mjj"GGGGrݺu&ꄄc$Jp]] c$A]]]$p[-Ǥ>ׯ_g###9<8dfǏsۿ|;H${wHْd#**j01sb֭&uZUUUFCC G >}bɜ8 'OJRiowDDJӧO,tVL]|>$ Νkدŋ/pHR\hiiARRfhZ & =={AYY/D;w0dơCpB~Ĉx"v Ln:,_U@ǎí[典8c-xbbbL 6̨ 7͛8{,<<2335QPYYG͛7PTv㻢F4tZZZ̎`gpNNݼyӬTXXH(11:;;!HD*ޞ,X@fΜiv;v0ɺ{.9991t:8qq577өS(++qi4ڶmq>|aOrd2V#= RŅsqq!RIv"󔖖Fr4 SYY]rl?'ۛV^m+hNNNDG 60^O?4zA6'N$""XuͲ]P(D^^I]NNN image/svg+xml log Low PASS BShapr-0.13/inc/Pitch_shift.png000066400000000000000000000024431405711530000163360ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDATx;H^OͿQi|T jIL.ڡc "NBAE"*cp(8U&jk[{D|!ϛsm@-@!('9INJR@Ӵ2JJ̲,`XrI2 pwHgg캢P iB$EEET E}N<ϓRd׬A}Z)IIIHS+$fD~Z)HEER O///dhhP%{SN6ߔ28ZDhImpR+}XV GL&Ȑ}L`S+n'MMMOB/"0cPkj&>n apV@ @I^^bX%Zj%z^1c%jjbeSdIcx`0l6#??_n)?2! |>??)+ajPlFMHKKCNN 33V477\J5r .G|^ee,;Jt15q?= ~87FH]}y~xx@yyyva0>>6ncll z+++HOOmoxxVeeeFMM 233m61;;^\]]lllUWR/NaussA@]]XqP< A|8irapa466XTrYo8ΐ (% nmm( '"h녅B  "NCii):;;Eh i3Ddt:]6ǃXֈjᩗ%%%u$UnDdh @Apzz ]\\ '''(y`q"2xggO.x022I~?.d'H=:12p\E___Ⱦ]XKJj$碳@QT$j @sVgx^>'Fvv6Ng,Ynn.n7^__#%O) 'IENDB`BShapr-0.13/inc/Pitch_shift.svg000066400000000000000000000262601405711530000163540ustar00rootroot00000000000000 image/svg+xml Pitch BShapr-0.13/inc/Send_cv.png000066400000000000000000000053161405711530000154550ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org< KIDATx[iLS]~nJAA!(nD*.U7 $R5H n Rw C@A`%Z% bpD!*QDQQʢp]PxIzg̙9gY)`ہ~t/1@HClVV2333Ã%5j %߾}#T*5Ç'G%n"4EEEDBԩSwYlV}&EX,l۶ _޽{ammmlڄH$Baa!|||=z4.^ʇZGH\.Xr%_ZZI '''̛7;T˗QTTTWWkGZZG󝜜f,]:1zZ QE)͍X,HRh>OgIII 4 l6<$''Y`xxx2 AAAPT9o<۷ONYYbccAP&&&( JM݀&N+VU{aGΎ=O&uV,;ȕ+W &d٤!>><reUUUә3ga( !<<\:(4Gׁbﰲ,Yiii'J@ypAAARt <<<3yyyppp`pC.]SmGH(ҩτN3 5x<2d5YZZnKEaڵ1bN=HDtO26ۜ_exݺunWϟ?w7}b%EgBBN=@D;v!\q& &&MiӦ3gX,VOր`}QPP_vvvpuuݻwĶ!V Oy:Jaaa&??p2J!W.\@ Bd2#{kS4М&CBBpYK{n444V^^iўEQ$&&:_|I$I.4 lڴ ^^^YYY@MM qrr2[YWѮ]Rl`5RՃָpRc#k t~-ZD#Gj߿1}ra8::BN^^?n`z4-[j9f,JIIiG\noHk~,XbM*J;QQQ/_āp->}Zm~@8;;ٳ2e -kllDqq15cƌAII rrrZ>444ϟ?36nȐO6 Roݳg|>>s8'Hjkk ?8<F6oc)墪 hlli br\|R ݱ`###[YYGSD"lmm Z>|ԩSf:iv???eeemY{{{̝;Woii X SSS_(ɓ`HKKCvv6-( bٌh6 ???H$8::B$ƆZ`É'P(LRǏFyy9 Daa-;;;@QQRSS!HCr1xT*ō7PPP@?|Ob033Sk\555x_- "22fRk2 #k}}j7g,IENDB`BShapr-0.13/inc/Send_cv.svg000066400000000000000000000200041405711530000154570ustar00rootroot00000000000000 image/svg+xml Send Shape CV BShapr-0.13/inc/Send_midi.png000066400000000000000000000056351405711530000157730ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org< IDATx[}LSg~@*nBeUuaQ2DJɤ0Ĉ-m10Ƅ:æ|ld8um-^nm PJa?I?3Ǔ@ pYNcQOOiZpB@o6577OMMMb 6М9sH.tn'////ҡq?Ґz=m6d ,;ӯ͛7Q\\ "P_\\f3>W_}7o瑚\cǎ9s&_f"!t(jdŎ<^?d]gWj]]\\LǏ'K6222I$dGѣGh4;7mDöm @RAΞ=fdgg#//V`0?#>>DI$,] /^ܩSk.^ԛxb@JQ(lq1 HFjA<4XL0 ' B .)a\?xrJ]UUdxrCRRRF|4EbL&nvEEf3gXDGG#!!,_*",, @!~pp0.__z%?{u7 F#n޼9 yiӦARӓCW(ؽ{7B!QSSZpΣB!RSSh R h8 #GG`0ólv:6&.LPVVFs9[QQT-++ :FCCK?p%Z >>>eZ/૯0(((@ee%<==QXX^xz=JJJ8H\x(W^^A.8 pNN, .]ÓH$xgI~zHRn0_6l؀۷ɓh40 .9{ndffBRqF;v@TÃN*b˖-HJJ’%KXz`` O>ɳb ̟?Ũƍ9圡/͋bT*;::ֆNl۱~߿!_RHLLDuu5KTMMMlm6/_#G<` ܾ}QXXz ,M"p:i8mg|p1ܺu#V1k,7Z-^錁) yq[lhw³l2|(--űcpB̘1.\?OűE̙3l]A `޽hkkCPPMVvFш۷s.ەl?*x׮]xB!@Dwi||Ӽ8~Gʞjmm;wSsΑL&#"0 *))_Pee%}GGzwH*>]p͛GhΝӳh""" &@.]rx%F#yyyQRRRЪ*͞=}`___6ݻws2# y6 VSNlv2e :;;y6m6{ )bp>BH$dȉD"l6]]]wC^^^jux=Ggc81=1N&">IENDB`BShapr-0.13/inc/Send_midi.svg000066400000000000000000000177771405711530000160200ustar00rootroot00000000000000 image/svg+xml Send Shape MIDI BShapr-0.13/inc/Shape1.png000066400000000000000000000025351405711530000152150ustar00rootroot00000000000000PNG  IHDR 6NsBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDAThkUUV`eK"ePIj!$ ea$DJ{>hhd VXIуȄќLu{3u(p>g=g{h m&=(gw}30*:߆Ul;`'G NSoi$؄U0o`PcCi5kmB tPjx~I]"SG+\QG=Yp%7Z| qM3Lnri\uWVQkznyk_tYŽF3\YXZPg'e;HpQSghdk\#뉢IH5 ׻KBޒ$c|гRYBndHFŧ7A2Tt`n<$-9af!fe/geq SU⨞<SqJtm^)b j]=Vna-pMH8.!x]xFY؟^Ҩ`#:+*O#Uyf'Χ Vy/ 樿 NJGYOٗinȌirq~z´O$?G,ýcWJƠEvMc6yT6狙/,[&+"]$/p0Ķ$^&NS)3UaGfv_%<)w$yhf -y'eea*oI-i# 0!|K;^j-M%ޔ؈cꏡ6# T|o3AXumQшe~1(&2ɢQx̓mUՎA!xEhTl&ۜbOŵ*v!ʺ0+2C//-_IENDB`BShapr-0.13/inc/Shape1.svg000066400000000000000000000050641405711530000152300ustar00rootroot00000000000000 image/svg+xml Shape 1 BShapr-0.13/inc/Shape2.png000066400000000000000000000026501405711530000152140ustar00rootroot00000000000000PNG  IHDR 6NsBIT|d pHYs+tEXtSoftwarewww.inkscape.org<%IDAThg\E_Lb &64Q1~$Ċ]Q bGD1 vb vQvDMT4c^|ٛݼv}.;swΜٻs甙QʱWRbFRcݘ[}0V T8 S1"7m~N̶pF) -Y3C5)MXr\7'o}^K uOGJGXB2ڬpj_b:&$bAw;[qi6'7kW{r]ϥbEazX߭]Qf5+Ŵ1*_U-%v 7qs.x8}Ru 54i%%6pqq` ,)?[r#I܇ ju7,gz pR U`K?He2.LZ솣8ۓ8lEb LϒX1L#XV^ VCbRoXKcj#*[_^-*fK=>\n@sq?wc>W̒:ASU/rx Q0IÃc)NQO`E*xCqpS BUx|n|xY6ءEUI!W.;W6xQ, Ur^r""*Lc"β|־u6 +Ӓ{O o[;T]+NY(+ԷZMo/`|%&u+m_9#k8FcEkf4nA+ߛٮz Xb+b(V{p]O$Ƶ=띦q1 ո/NQ< ەP;ܩV߼ :S\okN HNo肏Kree[*Ўҍ Ht5NCprQɸeGo}y3>) p'$ ԰\18WC-䧊D 3ߙ3Yitzg9r&͉5{/T, }$xJ?6,4\zE`Q< G -̯ $ <ˋc1Yyhy2ft6+cr\zV ́ qb E`my 1bKqV98Wqo(\/Ŗ١D8녮2grQ,*Ѷ ^篮IENDB`BShapr-0.13/inc/Shape2.svg000066400000000000000000000050641405711530000152310ustar00rootroot00000000000000 image/svg+xml Shape 2 BShapr-0.13/inc/Shape3.png000066400000000000000000000026621405711530000152200ustar00rootroot00000000000000PNG  IHDR 6NsBIT|d pHYs+tEXtSoftwarewww.inkscape.org</IDAThy]CO[4!{mm AibKRA% (T*D+( Z[-A֖hiyy1ss{_o⛜̹<3g3K>6(l;CpZT_r;K0@cGvXm:#px1ĺcGN )r'.5P!wyo~Jd^N[+K>nVߘ]؎stm}cswBx_=0t `nAx wCۍd'T> u.~kk*_MWq<\>ĺ-`2vay>0GLtau*u8UM|ed5v+^avœK9|7}1[}b4a"d&`yT Ia= a\qxdmb2cU8GavQ)܅gkcpR4%:Cv>SN?)0^VFyOgUb*{ Δ7nRO|b2x[S~L]/< %pdBW"+jL r7S/ѹ0**Bb&G>LD=e}s&$i e ^#dW\| 5xAo6Sg-.}1I0.gG^=jϭĹ(C4BG.8 3j/:c茬. c6WyɪEJ s-60!_&~UKL9сŽͅma- *io[&%HL- j-/ʷ|r1=%{ c[)FP }JQCmNIu܀ݹ""saO,bR*q9Imy TT&_0)$lעUP¯HLK4cYQyQ, dS)i1vqbq>-3_&/L!hZvز%yZ4{_s7aUJQ,њIVVxܧx#Ψ?3GX5bڔ%x|OQr!%$SVPK]z]CJS𨐯ljV|y9!dIN .n*{IENDB`BShapr-0.13/inc/Shape3.svg000066400000000000000000000046711405711530000152350ustar00rootroot00000000000000 image/svg+xml Shape 3 BShapr-0.13/inc/Shape4.png000066400000000000000000000025351405711530000152200ustar00rootroot00000000000000PNG  IHDR 6NsBIT|d pHYs+tEXtSoftwarewww.inkscape.org<IDATh{EL,2AeRꏲĊ$(0"ʔ$ RTRVdeQQٵ+1ttv }aagw7ݝcl#2uX/ťx*;pL| >͡ \s'h'SCv,OYwW㫔&`|Ϧ.%c>l{Xhv&̤6RCXT^3dzmo(|)GHse2ƥc=xHxW <'940~A[`/EmR I!Q jna0գ )lBvyA*z+/ P%]@1SGx)Ӆd .f RUt + 3U{.a|aj_3ofe1>ެ-j  h7[we".Hݜ8tޥ׵^#^;Mp_wXڤ̫CQQ K(8 8+e$3w[YCVQT4p=N*fZ_V,:4bh <˷4~zښlktH,P image/svg+xml Shape 4 BShapr-0.13/inc/Width.png000066400000000000000000000053011405711530000151450ustar00rootroot00000000000000PNG  IHDRx(5>%sBIT|d pHYs+tEXtSoftwarewww.inkscape.org< >IDATxL{yKP۰Dc1ΰ[3]&5,nרƚf-:hddZu}z%L./:zs^[1eh_Lxc:SL5 ;vwgy@ᆀXPP]]MCCqqqvW_}EEEvp 777nL(l6;vٳ̝;7.999#(7&IXl M .\C$'' @H$zzzfjګ\QQB€as><>(k׮3 gΝh4'N__SSnăwuq:UOEE d2lºuHHH?ܹsy=hjFF#7n^^ իXVoL&<.^`^x_|k?~GF#vbnIZtt4Gg=c hkkgB`X\ŋpBWj2|RRR Ox$I`0o>,X}?~O?$FrU/<쳬^@ww7۷oGV/--%&&={'FC\\h4]J%%%%\rvʕ+]uLll,BF!**j\ C[ !hnnnFsz׋#Gx sٶm۽{0Lܹs>lݺU8N[fW\8N !.ٳAAA.YYY"%%E!DBB%… n-[!vj!iii㞢}&Yl6i&VXS*$&&RZZc=FOOد!::<7|0Loo/n4ZMvvիWoɓSUU8v_SN|y]N#<<;RUUŻKPPиde2wo;<豞܄F :4.cJe\NFF.I;w믿v$C\.GP`6}ڿq>vbӦM@EE###T*ʨd9s'OϷۗ0̘ۤpu-c2x| 덕̛7& uuuysy7oo֬Yh4jkk}䣏>"??ÇBqq1999K̟?BAYYٸ|+6oLkk+j/pEVc2ȦݻEybnjaaal޼ك~k6hXx]CҥKijj **ʫV(,,^ϵk3g ###TVV޽{b֭o 0hZ=JkkwÆ ݐIzz:iii*;8755:NhW EEE>`gAfQYYSOnnĜ9sÇfs񆇇E}}WbrEkllfY,XIxI}pXX-nfy兆N7T*QT^߼VT{ gddÁRbxJEFF" ̄&LF\\kPo2fbt: WnBR1c nܸ͜9IskDDDfyYG?NckmyIENDB`BShapr-0.13/inc/Width.svg000066400000000000000000000173211405711530000151650ustar00rootroot00000000000000 image/svg+xml Stereo Width BShapr-0.13/inc/surface.png000066400000000000000000002675701405711530000155400ustar00rootroot00000000000000PNG  IHDRUh) pHYsodtEXtSoftwarewww.inkscape.org< IDATxw|u߳=zQ^QlXz6TWgC b?+Sr " [Md"|,K| *TAAA$L-*B/TM4J}\6tiJq`縉zNqrJ=kZAڏ AA{/~)-Ԣ(L QLxA O   BA%A u$O1UxtMAJ)g`JJl !Nll/z1nspiN?~մ%X31\ B   m"> PL%Sᧈi$F+MsqdAL6gF(S-rwpY^Y~@ɭe_^DGM-ā%B猳(AA嗊҂BS~G'D)/>$D T'ߪ,O.c:4Ir,k~V1ha 'GOZAڏ8A^  8%@ }L@RPPBRQPHz"4BVP&U[]YSUJU~ݕ;sg:W&gd蜢ޜ]O.! q` w8A=^@A!QZh(-@bJC1jCZt3 ~(rSDoIZ0 &e2Uv7SVrʤe΃eϭIWe,,csmS9 yF,A:rצ>c?Lu]׿A!O$heޔҟR|6Q˃/2Q`J胟bu4+"&dX|5 kt1-^\b3# K#! t(l Cg_jSR8 . #N=a P ^x M!tΥq"Di!L*6)1,iqna sjk\"4Nᅙک C+1`^͖ NxJY X ^{QW[׮*arї3{lJAA:a ӈ_JM9.&L/Q*VmB BXhBy1(OX !!\zե#sģr;yz~cFaʤ)X  J(4RM*6 4RM3hy+1C [8մegr;z=[[BSC"wى;!@ %C s Bg!,A:QGqEr胏2{}veGn&;a  l]ENrkʲ|fYƕ[[糷1Zנ%Зb*)7!*PRm,N&D5 TW-lJ%B0hOzÏ>h4C>u]Sz#ʊ9x;b  l,{2r2jdُy382"`  89-fP9pOJKedO˾{YVxӋT2ZhTIU& AߙRIiEf(#L ?EX ޕ)+/orӉbg,Umҫ.e9))-~ͩǞ+AApWo.ŬcY4UQfdzYN־քCU,kwxӇ-0sd̔p?yߌqHVZI[~B d;*JNV!IA+}Lu`np\tE<79v|G͝U;bɂ  Y}ooeJ9żޚ9XֶNiޭmTDPP KK#قd ^D H+ DiI Y W0iv<O l> fg<)LڃX t(}eSrj~Ų}ݹҫ WOHqIq>* m^!+w1+Z×~n;:Vfʾy.ҒȽ3G?ƒ8QZ@+ĉ)h$biIrj9 lOQa_" Žȓ3 Tbs3s~왳]_.f7nk~پWA"YbV涙[@ aΎ+,fiE(~n bg|HZM WE+,mr,+;T2 Q^ȗF覈%B^3v G4s3Q[bue{sr}3ǡ+vdAA:\Ĭ|6{7TrlbyT7Ǖ]0* Bo9RB_TTZbDAd)C+Z qcB>(O>$/F_  Bnhg^4}\s{;s%}מ(DY24Skmcl|S@ZG&\/S,1-u%3G?!F7?1;zy>a̙ƆF _~%/=k8c9ЃӷONVM&N88.g1i1&}wܗ^?\ݢXE^  B$&Q&9 RD,NbN*fyDU"`21TXd{@wUR`Hiߝ(D '1 BXZV^+.y?b锹~?Sʂe t&4` :yTUQgޗxc}?psf+.9o0rˑ|/8tCYP,AA s8k}l3Tu 3}7UĉGT/[2]'J+XEf?{+ c}BsveKXFRZ2ӹQ\BG XVvy^}U~g2lo|:B89}ٰvuSyjSy%>׌=O>˅  8FzY.d9Xj8.fEhąՑx31T1fjg qb ^pmXqSeNs+NVz1J6+ (t+z( gw&<O/W1c}lA$Ϸ6o}.cvO?Îg}d{s6j]  ]ga3,w!Ta'9^8q"x ৈ(!dqjYA=,-<5v%N0kb&G>JՏ}.T"4#H })/k(t:Mh"g_o?|]wձ2ߘc2 Bf>73/su6[4M{:W}uU0h쎛9os 4?}~p`nƼ0W}\#]̻o?GNXSz>z#,oFW^z/8T Sźurqed! >_KAA.f"YʲbXm.M DX$D%%c9TȠQ!g3@]ZFQLty:f5މJb /;=.VkK+_qk}2U1%z~V^r{nMW_q1;sVqlV\p]<+y<ϧOV[قwฬ~iN>dje.h4gwu  d"vv, V 9+)C?EidKj/;eWe?SOvmy79nq_V^ƥW]sG3{l>x烬"ls3inݎ1p@Sǃ/ 5х(\d}ftϴiDAbC/>Ze?'y@]2)$pgrknaOnkmCh&ǺeLIO `ߦ2f8N\Q0zѼƋ&BE!z!oT+.+.k/g^ǞWD"ŵ~6n[r˖.#n<   e1˭!P P̎BPQYr=.E)e좔1cUl O8cPE71,<~"w8)Nqt[tIq,]S]ٛ]ؕ3~scpygrew>`mSOzMu<_!P1{}O  s*msMOn̩VH2BnMَ2`?B)2yZӭ;Iqi; Zw%RÛGgKXi z?=΁ջ7GǺիVKeeKݢ+ʙvn{;pӝ79:m:l----mԟVO70v"c9;mMkYbyP:ǹνܜִtR6m0ڿP>٘tν\>u?p,YAA#or΃t`AWPv$S4S sUvFוщecZ CL!fX[w*4RJ_|:.]ݎq 7f6mmS/JK{ԣO֝:Tf7 OWoA!tAz-ۃrߎm4~^ G`{ 0,~/3a+:|(#,|Y˯cڍ_z9  ],{,(Cy3`(QD%qaap_e*fiaN|v,6YCU>9 ĉҊB/wލ< !qəOvcYywW9Me>'M_/k& еb H'B#AQA3X'৘삟bay^iuB=O83F=P/7cOMC 퍲L[]FQ`j!lD~!̭-?|bXv# ѯ?y(->Ni($N-Fma+{Yd) p }˵ۧ7ry<*T`5I.YyA<g5^ igq;AAvg4ʶ`&[X-$#  e ~ 129m T3Zz:fWŕw: OL-DWo}/7/xČf8&)%nyyz?2H늬X±ple]iǓ}2{Y[^EsSs% eQ$feYҒћlޮMb*® ְь•]rҾC1Әs)ܚkrH<7Ϝ +N<2ιS(~0怂;* f]'Č["va\8AA3 Y%e%iaSBZ.ݍ^ M6Za~Y;U IDAT2Q9ʔJ/!|RLbjaΉeLWCW3_)h02T(@%t0BRPkmڼUTVU@%>;³YjFu+6j| [mh+~羾&g{;pyg'n]:j 'UVUʗ1sqģ]_.f7\  BsabuT0b*YO)WϦCSEr:SJSIVr fIGu9 TVʞ]Bܜ,^On*—VgaZPQ)>t^ikRPO>mps8;`W&:1os:?_9ٿ?]R rn[Cgre1rˑ]ƫnއ]kyŗY[6kL>Vɇe NrPePXzTիVsn`ʤ)N86߬s47pqc!J:|(Sj+^SʹecO3^xˍyYciםx7^y+J[`+_p-+;ugPAAMc Y.Y(ѿ3WpΔ3kXD#%#|Wz9Xj)eqS ٝ',>@-{|Y "řiu] ,%n~CD"^{_g <'3|}aȰ!lBl6b39xc~*sN?nҲR[?:zKAA䰺\YMЛa)#@1zN>B f'#=Q@T^cx´k}OAh4J$aytMg㆛o/{M#Y=~UpG X5G `mt,oS/+iس W^;xgsgyxxϙl*yuLz*SΝTUˮm:Ͽ|96ӷE p_p3Ѯ5  lj(xRBVK^,H3rQ!D9A",f2yD8fV{Z\C[︖׮PMjW[hg=dzח/}왳Yr|~MpZRZB$x8C ae+*ۯ/˗-%]HZ[[YlEOeU%c/kL9zvEE\zեǶ{P2(+/&6mH xL8|v*JJKⴶz)*.H>b8kP$S;6v7zE,K=VZJ׺ޕ;q%Ϫ\*6NZEsLecӠ/w0{n' SImGg\Sg,-{yퟯѣ OL4븂  = ^5N\s5[ >|x<+~ʋwS&3tP͑ _+?&?yOiY)SΝiǞop09<3r$jQ?gg]:]>3.=R&1'MSb=wo[Σ>[lͷ ?ٟK2~ZZzdIikS[0r-{\Bp/5״{BUUnkt䤍8AAz*Iʛ6^v򤾒,-PF)c Q.^ /PPhjjuqG]̲J;*ա,sw:L2_Y./u%4ݡ,ЌTIL(G feXqc{s8Ø7wWo^D"*~wkyor3[8@7+.4)OᔣO~ybylՖ>5~}ҫ]ٛڏKνÏ>f峏?HPOLcNeEϙ,n 'zTw#&kzڛɉwJǰ͆9&n]_˟즻n( oz ݿWwqLqZAw4xgs=΃  ] />|:Wd٘`)qNƼ,d-|UЇ e^F[)%^936NIٍmAkRts*\tJni TSJv-9|UX0&z!*tOL1pG3v<2tjL汇KOқ}u]&[3ߚ۟ͣ=?#a۳8\O>ΝiK(2E|g?P(Ĥ'K?p^u y]?ǻo_V3d?h73dQ-,NBk6aQ >Co˘]3v o9tPQ(} [<O!}|`K˖p_ncm7_iw_r73mZ{{ &A$I;pģ|d@2qQqQ:}ȸǥjND0de%^3f=R9Bqo' 9QUյһ7Ov_Srs1G|G|4}x8sڵdiYm@EQ;˸N~WsUmst5645wwU+Wl  :^|x WN)d F!DTC+S@=[(JŃ(-ftN/k8`^uɜ 3&7ϡ0j,uGt^>}<$ޘ!+"NVyE9pͷ؜9..9^,Ƴ3eK3xM'>ÇUΛPZZJ8©ۏ=5ǟQS]+c\yv}Wئ:Q`Q[peQ: ģ>|e?-pȑko۬^h4Vf7_| - 듙~tvc7Y[foF~}Y[6iV(JA O%U}ۯ/;C37wYXWT\ĨѣY[I5~]k+@ ^듩rNt{s:qe!p7UUx<.K$L<}Jvxnm߆/<;4v uM]pUs15нs  u˙sIz2Vx>]@XT2{Y~2d4.fi3۳g[3.9ڥ; ƼY$>B8~'N 飯ʚ_p_nY}Y^={キnfpEsQY&F#G'"7 Eay0|]gC}+oϦC-\4ٰaC}pG0iv/>"'1,$uYxˍ|3//C un,XV.\Kf_x>i^Sͭ[5t"MD"9CyE9N8T( ^  ~֐ѾCR"ƏP~e&c_WKlC1U:~6A,f)6FI5E!vjM3b78>]+I8x+QZ RB qaX?q:Ue횵\}նoIyd8⛳ߴ?1d3OG leeei7כ C|Sz{8HZ[NwMݕH́~/n+p~)uYO;syg^b钥^F2l[ycC#7ᆼdҲRn%t/H3?NQnξLAx2W`|߸Ws&$;'T ӈ҅SKf*DY׬g{圥n0g921Fϴ CUn9+qR,+ך ˞o+aK%aN<ϧu豺_r>oy֖V8sJ4,]!Ϭ^3O:L9wcv݉1{?-_Ue[﹕b{o7T,cSNľ9# L+_*VYW$v9 TJW_'a,jDRV[;q걧V%G|}Փ~t/V%zќs;tIJ8㨫)k  BOAAO0,Q՝D(T- bS@Kne$@ ؉l?-Աl`XI2/1'9'(%0I\3&lawL7sH>b8BX#&^~emNxM$+Df]&fϜc椣Nb]ͺB/͸%nퟯ-HVʔȕ7\١[v7_ȩǞJkkNQxא\AAqA`,eVCd/sT9RvQJ"i| 㺋]aD7jq_A8$‹Dl}BVESc׭fm |r߹bE>pt~Y[f/>"+;ôu5Y[w~/Z,"Le+^eY[ؙ1U(ba|ݍ?-1~s3K,n[ҙktΧL4IMr}~_AxI?ap|x4+m=xIw\QY7c+uGb؍OJEE'D]z_Q|ZsR۽TM;Zw&~7| «θ}B1-2Mn9\d"nGεv9*Yk[+FIϜ{RJ_,GȻKAA^|)"~b}WMQJZ-\,B~HJW_ipe]tOJԳ&jb%AT0ji2QRRJt- TS 4Ykr*[na1=3);Ϥ{̭2jI1|rvo2;TSosQ s(i11 y6YDAAz,Q ,"Fiɻ)0i&NR9݇੐L4)t)lˊg8ȥN q"T+(geJ L(-$H&| $!(4PMNq.fbwdt(y:^*͡3{ϸj}񪵿$rKAAqPJ>J{h Vx^􇭤{ Ein%5UŨh߸* <)嚺.jYF })7E"HALfji )䔹[,{xNWѥZ3 4̮+d{TC;3X,"`  =)'D |%*wّbSRBVh Z˩Xۋ񠐰݅8QYE=P% (&#BV4JVɪ=b[؟NY%vW.?—YVrZe*DF)MUs`"  #᧌JcZT9W)QJ MKXa/FqKjb&$]X=ڼJjd&>X%&.9egStZ9t T۪r=Õ 2{d(i(>1ЙxRB(JFMbEAA=!J(FM$SY%.{dYx͒ܧJZh l۹LN(*٥|.);t|XMvWVYkrseZ]rtzY3},=@8&Mۚ顂zkZ1&TT4#@R 7é`eGImXZj5T3zTP<  rS!% DN”X\K 4LBOޔ_=!{\ȥ.^dNP/XqN+G:6Oۃ XKѦ8|fGٞ /XRꄮC)he˴Rsh  m)"JQI:eR;ruXБHyVԵk,pxR3:Q 82!wV.YPgJntAX IDAT*}sv# f3ΑN%rQPPdى^ M'I?72)s-LߓKAA薔ҋou38Hr|y0qb4?e:qt#+uwN{By3OrkPv/+Yp*u'4wree1tߜsB_~j!2ن]ಆJ"B`8c)7 JVHm9iɀfr B- U,AAQLYy s r2p qU%(7 . ] /^u^ UpP|^,1̽.fٞNb(d顂zk.},g ]Z_N(iT{)eҗ-f+iB YX5RFQK?V<X  BGTxBАa-й=,F/~Bċ5DaAgYn s%_d/SM߭N̙UYE.BF(Q ᧘M@v`8c J+PnI TN!c4Ǖ.\iw_,AA[C9$CĉYZIva)7a-NKIJ@gp_ux^r`sM<(i OĬ̒=`ϩ6efoJ{Y0STCs&/5ЄADdBT0T0Vh8]xUBkBJjBd,q` PB ~GǙ81bD~BZ nQ Ĉf MpTbD*h.‹/@9|0?I*щ%tYnJe1tbb90:3`RL3e *RO UQA_!( `[F7^ĉ5Di wҾ,YcΗ/Gˑ%  %To'S?yo!c4H-D¦AAJ r߶93U&10m!Q"#@eАushO|ܒwwU`c|s'l|,NeWlXhM1˾kb.b)КJ{ekI++PA8Jh^$QJZS•.VW'M7 ^c+B;NDACPЋwˌS l󑊶?K:X/Z=2z~'n3ߛK4~5 Ĉ\X%0Yphx=F{ =.VՙR)fYRD*Y72dM d>&yv}aYRbDl+(`F&K2'W9DA!SF!I*C&G̤܃J0BGQ" h!ڭM1ro+&N,-–RAegI}X-ct6*4r͗e /tjc}ui|'1+Ir'W<`o]{ 0Qv&*.fb%Z$@BAA*@}eW+  ^|ӟaԳ:Ehy (6,d^C((x(\=&my՞Yro[5(C"-G濝h_>J0(&F ` VYKB .v3e,fYE136~E-xHJF0xYDz>*ҟλ jLD.=m']cɦd#9X  l~ d~$P@J>ؼ!$CͼCc-i% ]Rz1"&'7w7:>0h-VIo~J(ZUH&?SL*Q:^m1#]sn!E5"71({4H'lف썂Vx> Tý2 [ocNߍ)ܓazxap,  GAć SmGLwA(FqE_2~ĈXvslww8XTOLJK5>T.!J@jX2L$Īrui!HE#"&NjW(:oeޝ?7w'nE}t}Բe=֌W㘿hXoޱGcXG{@ $@L<"pM!i4ϲ78ʮWek5xUС{ߣSq`w,A=hC8wG|P4K@6+p $ILb-ÊqC@2S8t!2klѣ?uj,6^oypv ًi*4XaXc}݌$LYcKp]LNEVY¬del``.?2<3.m`P8 `8H ωU3:{|qW{" a$Cˬq+lp:S?xN@&Oycgup (ѡEg8h{?4$0~??*똝|C PW.*u1բW/Zt<,&XҡM0n}\mk`'<.:[WhDkjtV(R5ClU ye5_AԨ_sd%VpR|p@i].^,4#.I e n +lPz1`j,:}]JTr[ӌ8PiRJY?1?׳5eU,Ѧ9yUWɟV++y}zUz wB9w>\L.!/;>w \D~(<>U]j<~Q=V!\%,}4ָN ,I:TɫMԁ ]w}_&Z;h o=Ulr%-gu^B6 i1g8jz@1jkzt:RVTĽYUhYi4Os:SByXJ{ʫ~< * lON\gU:uT䏦+dItJf"Kwxz[*;<lp}v&tR;gwذ*kk:(+T)QKg}#t\n ƕYG9`z&ܤW˧UuXf&5¨I;ŅǠqZBy|\Rυ$Wu^8 VѼi|3ҥNo\fm+NKvYjd72oR9~>OP5*&}y6tC ulֹH2{У3fɻ+XXkQZ+\Q5x`^"v<K*~Q\>uaFo2aUᷚ~b BsMquhQ4ĺ6Oؽ.s I9-4 }{'W֧OuGeujifˏm5zؕ0`HmC~ƧSi~VeuisXf$qS{fK#Kq0,c[]Ik/?WlMK:a*lp56YbuxEur׳G&><Oy ^e5c $.oWoMn%Wm¨  W$?&n%7d*Y"׹gs%J*N_?~Jk,wͬR7Ċ W_ꜞyy鬈f>ED+*)ueqk| <.(h1~*vTCVbMҊLvpW~}Aϗ OxN} tfllxߧA/8E-PggpZxPBY!RF*mhWZWx4xeB(K-{\ VB 3@W>=zyu.p*g +SUM*o7-dNVVO?6-dHCkP^9sXE[vY-]4,Sg}v Z`(:fFAk~JopRB2%J et,2>Γx}OXKÛ1umdkl +HSo0;Voh'7|k0Sڰ2WS<|TnCykG%g~f V:7x'O,;Uҡů ˃J~q|l~,JVNXґPm~J@@6mZi$+Ǟ'{uz L:K\6Zlcs\:=zʺp s`rz4\09j3:mw&UjTO7qZҰrsѣ66ߔB[6{j-}Q@0[îsXa*uZ챟bK~߃WiPw.îKo{LWU󌯹ī,z7'S,uM@_dT} ?yikìdlFaVx-wQL~Oqx\,i6.3ե һ J"x^} PakԨG!\GBW%JߴA۳zh%-H,F.{?d;^!='|\Rϣahl 5U㷜{ ? u Iv+KQ;dUsMo7=BZ'J+=,pc W)&/7~Yq&P+5i .R.χcӿׇ31{,fŴJ0[T|?'+4)Mmָ<+N~ 7x!Klҡg>oҞP=9d(Fxi]DL[~KZ2eʅax8hWK%Oϳ֛G62[\w ^e n9^w~0D2LRiҦT`'Á+u4.q yFӬTK?;ʙ٫d4v霫*y .ӧOX|EU8-em7]ԨSJ <8blr]:Wy7M^$C~Ϳi-Yr*'F~{օ0fk¬u`; Fa%R@[|&Wc&}G!en?1H;=SN\さXVKC@=YiኇIYPۉVi%?_UVhrv5 U.s '7~6yN@k-d=OMؤ+4X>xW{l6o̷9=UjiQ|ʔCwKg54Mys%.pר6켨Lk˼N*Ϲǯ "EM FZ5׸&y~dng`I"os׀=, рYFG_yޣUip+<+O {3+C2( /-wvx6]o, $ϕjUJTM" Vcg}0긪$kòAMggaU g D%V ֹV|)_њu3 :[ [so'iFƟ@R㛌& bIaV~|dqU%GH~?^J+}_ "*Xa6R}fm|#,,RoGYbmv]BBuˆ+/eL ,.̊VeMJJo'- u¬sXpۼ·o(~ %J5:_ wۼ L9'|,[ӖnTipk.%ɫ\Exa`iңWxdG&s+>;|?JhdUn}z|ůg~ceҴn!qaŀ`oAų{cO9L,5hVXF.=vxI:~QT 9I%`>TӡEmZ9ܣoprw$,;̚lmM\6Y #X-Y\[>:ҧGoX>*K@ۛ(SL*/./&|?ƭYP|mӦMZ`*oUJa4Y2=ʳ7-vy}.pWxmSt2]nnG1z\v̤ʑiU%Yä*µ5:vpeVJx8\`'X4){d%'8|5|Q䵺yC6Ҵ;K2>њi MϿG?Mc{y2kT^ (QN>dJґg~-E=b ǗGPg|~M\ݣQ[㲋dQP辌6m>dUO.=:j\U`BХM.%ʔun;<3 8c໬E=2>f0HlZ@v-3Z29?ko4)Q= -ֹD=`I{ӧukO2-p?+4Xv:_լ%J\2y4]56k0UG.DTTbU M'Y5]f6fem/{Jz犃}]w8M^e*tbP4 {k<9g]OF1V2j,qWٝ銷rwB1P2L ?5ӦZiV&?nbex[g< 1zwR1w3YWo:i1ôht%?ӠD \!Ǿ:ch{tP wAx ,>JҢVA5{iiK=O[2ZI_6ZCdM6>Vpx(d&;Ļq(~9+ޭ0Y5أFܠJ1N[%nѦwg*)D d=BhפOrFa,*[4X)"?Xs1LffBrnU۬s:Ki j%V}z<#yE,.XtH !,Fe9V@-3+ϊ+-";XXUʔ'U: s=)=ZxYisM@MouRUV}hf2m~H@}vh:CÕ,c1$k_cf%CW]:ip+,}8ofTʻ%<䛃ہGèʫd Tyv5^#o;YG4O^>;qeh4P:/w  O fy^~Rl0K\5i&`[;ңMkؕp'ܣx(]&5NSR2ϗ3}**N(5Md:TSw˜!,Sau5_vtʔy)Q hǮz5?8qG\V5pk%Jaߝ 3_=5}a~ˀ'+Qfx3:[,>lOל2lPbggk*+lЧ6ONewɳJ'J UamV*\cV (}:tx&IG&o9>gb!>76+bwLLNcXէ9]2W½1CmޢJ.]R9&=4>=L+d N^*dޥTi<0V[`dFrK qE7eޣ1wOBd/Y5ҋ-A $t֍ߧO5~i%VTq7y._KC}cGAVvo<&eS؞m>cijiQC4|Obf2Vo*ˬ}3rmq&vy>c-lEMN3@`ϝnE+l:_i1IqpvG@TЧG=:sPFgءC;$,b8yw0;|K$[O[hw;~&'*Qf l2_MD .,. ]-?qtkEJG^p>\.ҬQ@QN/jԁ^ 8n@EaרѠ٧]@@ >,(F9ow ZMIRq|A)1U$KI/ 8]<;;? G5|+Ru۹p\I_p yGWeE[}$$I$IΫ;ićì R2$I$I3{** $I$IPu 0 `I$I$H:H|0+K$I$I ՇHgaX$I$I: ,I$I$`*yZ5 $I$IpaVxYX$I$I:ّU _,,I$I$hH5oշ $I$IB8=̊YX$I$I:2nYa֤,,I$I$#-~ IDAT0krU$I$I]2ωYX$I$I:Q2$I$IR!du14$I$IRì $I$I4$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB[x$I$I9Dx{MJ$I$[hui&%I$It-,M"_T$I$Ib:|?E4)I$I$ ʔQg<~$I$Inw\:MvW$I$IС*n&:wOڴp ,I$I$-6o]<>W$I$IZ>[nI$I$I3Xe|?%ޣO%$I$I`5X-ӧOsGo$I$IR&>H%_x̽7I$I$ir!W }4' 8$I$I`]2[5Jc9}G$I$I1:V/q 9CII$I$_lp%&vh~']Op$I$ItU2Mvg3)'k$I$IT~ }z'?$I$IRD+~} I$I$e*H$I$I`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$IThX$I$I*4,I$I$$I$I K$I$If%I$IB3$I$IR`I$I$ $I$ITh nrsMJ$I$[h"$I$I9kMwդ$I$IBc*TewMJ$I$IC.r6m`MJ$I$Iwywx=r.E$I$I@0+_?|'%I$IFcʔqK$I$IZ+>#.pvO5$I$I`{t1ӡ`%I$I9Zg_e`^L$I$Ib+ G*Uro&2pX$I$IZⷸM4jb`I$I$irX1Ӥy0K$I$IG%WUďe|^rX$I$IZ\|n F$I$Imju|3~FVd~xwB(I$IE`(Q'|D懫 ,I$I$-||upO?R%I$I$Vf&=:«$I$IHV@G|D2)ws0CI$I$I4|`=~&e0j9$I$I-`HZ5c`I$I$iT9_Déx5֤$I$IҼ";ËH?姉I]%I$ItV)?e= ;J'I$I$-A~@:_ Ǵ2В$I$Iqki7Mb`I$I$IҠ̴߲; CiY$I$IaU>CYw F'Y$I$I"Tm:t+ jZX5$I$IHt|3 _Uմ,I$I$iQ*_5?hڤy$I$IUK.CT}-|%I$ItUFwӟej+I$I$J+lR8X$I$IZJ=zt_-Z>}?{F}quڒgY 2FX@X(іRF )eS l,E6$!qoKt?4,[-gKO? =l~gU]X&1**8 ;KDDDDDDDD,B b_of3?'SX&^ņza f4۰MT%"""""""" 7H j2l800ڧ,LXA6@(FOX""""""""bz` $AwT%""""""""I,n&|$tƇ1is DDDDDDDD2UWea¬e-&&{'OX""""""""bC[ r8؏Zݧ,LbTAVXNE-SXWjacgoq DDDDDDDD2&fZLLF1RJSSX&qęJ[VGو ?')KDDDDDDDD,I زլdgvKDDDDDDDD,j¶BZjYz a}*$LV5Q5٧,T:Xm}TSf6cd DDDDDDDD2bb{'}*$SJ *eWvSX&UϪL>VP%""""""""jix`b֖RK)`KDDDDDDDD,NHn/}P%""""""""KU=XV A1~S%""""""""IH%~*j@Y ^KDDDDDDDD|t{]5l+01م]pDDDDDDDDh޳*QlVS=VI%(0LDXKDDDDDDDDg ߰"QS=DDDDDDDD$w89#)j6:fu`HN8qp:џ^lb+OA€d=X""""""""89#B ޢ@u`H*`RA 3yTj`H'ږ~XJu*?nFt81‘;3G5umnKDDDDDDDD:zs-Eg'k4+ DDDDDDDDؕ5@*X""""""""bv{q8;&YĻ|E,)""""""""bc6\h ė,cKDDDDDDDD,c`J/YF-vSXu>OXd>mIDDDDDDDDz<5ڢKDDDDDDDD,k DDDDDDDD$)KDDDDDDDD,k DDDDDDDD$)KDDDDDDDD,k DDDDDDDD$)KDDDDDDDD,k DDDDDDDD$)KDDDDDDDD#vj Ÿp`!BZ!=^ˁ&&TvIG v $ugX=Z0`+QKz6QTPM^X77N d3 [O u9mKnob ࢁ eT %ذj9]UPM9c%*iHLDŽح4c`p*2 B ̤6>N[HR=[?J)a=Cۇ9,nr$;miYgI=7i`XeM-۰;é}HIkeoaOְ}S-=ZmhՊ7m[䫟1!g1kxwZh2Nfzs˯xN%_^揦d s˿'$ӭfCI{W/}9Nd8aD:N{^+Eg6 b`PA uTWIᕵo;UPpt(a~^F撁6b`PNU'עV>>d3Mzwa^rxu(gJ T7yױ)ƇA!REHCE+CB(ˁb F+vk`4r`Eן "u!+˗P*- „4荤|sy;7"٬ls=8qѻȇA tP*~֩ŏ\2*WLL 5BBx#<=:rHa+z9VJjϊ`u1~! \R i A)EQByć;6B(ܞ@ʪ# I'J(dK(FY8p Ib/)3UQG/ ⦞%sw IkS"C+{Iu,{^SmHMjt0~P;@0ܺ߱ZM#"X]L!0 lo|qa`FhY$(WV ꌭ#=ANNٝh,jTkϖ^T"D-क़6SߣQ^\ر$o3 ݏu~GaRA5)Ƌ9Î`79iB8qЋ"Bukn8k:I.HON6h+zrՏȹE_Uɺj(3ROatHA= ݲKgaà ;5jk"[\.)Ge7^>@$(ZJ(!B]` S/|q`&@ !GDʢ۰{ :qć BE~jW> )" ulBtgĿ6 7.pы@U:=jYzh 45d=t8~&PE-(‡Fk >˪{RbÏx)2*SQw6 JPG&L"=jDžo^x=ۗI ~ǯ7DD(;a̬4e >f_3Zǃ"|Ѱ@os3~~E^\j3XB@u IDATqDpwX fY rHe#D(%,{_է.TdI\Zϓ cD%Ձ=Ɏ ;6l؀ظJABA .-a`u{IŶdu"quJ)dD .p~8~JO|j ퟋ-BjY-~\8q᠘!ٱÄmazI%댖@%رIo$.Y\1Juzu#B)|\7@V#>tЌ~K[*q併s$ Dk ٰQ&)ǁ7x^{}ttH4``Ps4+ J-3$s*j11iPC=tWckKMLʩ&@0> Z70DDDr(9p.xpa8t0P:m~G]tF2nM ՑL_{f7z˜qAblDjeJg ؾ "TLAN ),=uZ:T@vP=U'x*j{|9\w 4hjjV --͌_tm2ŰtY ]TTgx32־GjhL˟LΦ 2}:i)b+kwWcRN% c78YIEDDD$tњ&2d$up4DK%5 FE[潤yޖ见AlGHC:9 @ʺH5񞂱ޕݝ7*ՎSl1B qэ+'zQ ?*uDDDXy-vG96=Vjt җ j uĞ1a} e}(e 3ã'D,$Jug a/s@a4LkLLʩ6 zSg>A={"""HVD'm}I(ձo 2hR_A'l:__/da$S!iu< 2? sn=ܵ/vl Q9ve[o+GiBau{쌞"""{ߚb"u[֟mHw&&⦘T]INw}tF֞o!j=8q^76l4$$H=֪hk{t5ŋRܲc6O_yIG5u4B:+\{Iyp " #%?SSb LEIVY"ֳ>. ƅGO&6u^S^R3E.kMxi ms*jsH0TM>8qSB]t@,O|zTR%7'q's2\41)le+/".`٨ݛ-[Cu&˶~{֭[GCC}nбѼkutȣdΛs uY{^>}ͣ|UVmlhJ .Ml"Ù2.|xcFgS^R5ݦ?(/?F-=4q>O1Y d oV (:ުu@>`.Gs9||[ڈtϴ|\&$VgV^80A|Ga R|y,g9_uGo)؞ɛa ?K1|,e$=.]8qڿGm:EUvT,ABѺyhol81LVyAJ(qj?V:C9dP\߃}؇#9Mlb k::'suܳ)yO>x>,55* gl2|W|EiR^y֙mreESxGټy3| cs~R^z%N;4n6VloqtYI>dOكMT0,۱d'6䦭a]]5-Y^?_9GO_*;"^=ɓTG >Sf_otVABЏ$@%||lRwG/ᡞ'˾|genVRs~|4GID-<3аXpf6Nlu1V*xg[ACr, )j*,FmFgEa*jY(ߎ7FF)~~K8q?8Kҋ*:xąL";F07ߓ>S?Rh>c&3[v%+`ϫ(čꩡ؍y9|3k|;s 3f@uu5]̻'fnݺ:g~;$d>x L„3'Xr%;10,f9S>>\ɕ) Eآ}30lw )6r 0;~$j|2bws.>ЭkTj^f65veg,^B-8q&W$rC}09icpp@2_Sk3׿5x̛7/6uvPIMYw;#8o1nGL"ub=b==رcD۸I0ABB㼌+F>2?2 .fA j9黓(/%@Á'0/zvmǑGGf<:l޼9^{=::WL%cEB>{XBlƎ+Ehe6A;n.ـ0"b,#LePq;رNܼNۅ0M08SʋY ]nGrJ;aJ;? 6c=()-fQ]]%KqqB}tZWĊIUb E4x]:p4ȫڱI(jf^t!QS]/0֬Oݼ7IǛ?ߓd]F oGɠX:zF^AAnOMM ÇgcŎwhOCGۚ+VbŊ6k=C:ڎ;ŗ^=nI#G2yd3e˖utSC8$^>0!|}ׄMCWNHc0az3lRy'd_A07V|z {ʉ'O~#S/[onqFx ^y-\1W9SD IO`]xQ@zX} 0l{Vbk?AVEu_.fB5]U* 1r4|Ao1r"LX F3{05PLWZ*E_:T{Վ,lr)Rk~M}QBxaQ\\ D\NtɌz_9]֒:tXz!c#w~ @B9_{ls+ d%+0ğ{=g9L?C >%,akw[3>ÍoWQ+pI8* #ӯ{^^/ӦƼ>%f׍wdG2B )-#[z)++o߾>}IZy&oᆬzJO;̜9iӦFq|2K/K/OÏLpnƜЯ_P忾#-qs$-7l4 @ Y?#yҧw'}W:7?` SOj;Dz'iy(@ iʕ\_xv؁M#ͷNg'swpӍ7Q]ݴlEUU˗-gܸZN3:MϯT'3O"}0 ̘9?O-n\t$Nnx3<Y6l0N8+^z!R~ߛ/2^/_{ev &sݷ˯f[|'|Y[_o%D!l@ !/^1-ȢG#V:8w(aB﫦TL_cт7\sUכPX059#{GsQ3+0+WWɳ9vL( /]W߅w˿?`ݺ1M޽{? L~$~۹g_S>E_W?ۛD_B`Bs9c{W{kE;[atxW--`ݺuzl&ن'(wp ;oe,e_Fs㩈NϞW;2k,4Yӟߧ~ȹ_~wZ/w~c;ɜ9s2f̙̚5sO_~y{NZ!Vbxi&Njmr2'gJ;eaMV\7ܾ2Bա+f  7җ8gxh &_LZv=z4w}'O8˗s̸cg=\0f?5{uNo5^g;~|?6!ĻȦÆA?J10B6,Μ#3XkfI(u!!n{x/L~#~|GlC1T7)xƙg0c挌8EalIHe%+ӧ|K9Go`.^ľܗ__~6;o\\*pgg֬Y|m9k6mZЉM7!_gرYoPbmΦjos o `ӦMX#Gd;sZWZfS<4d}5sa$ƖV3CXu+xǏ1Íl9L`B܀|X#n;>QVVe/o5sV|Msasx78c;2ֻwo,_B^˘~:t('|{{';j àމG gTJ/Z$6LUӯMna{=&&Ŭ$}A//͙'^j)EC .d…,Yk&d~6g ~i}.zŜ7^^ɲ0rHvex/Dm}+zC+𮽽UlTOlY )b^E A8Z*n Ͽʕ+ܗAbvkdsx^~iH[N'pxǸ;,]:AA8⦔Bj <3xUom 2y 3ɝwdI~&‰0X5ռK׾mk<̳A~n>sכrL>&v6(.۱tWO[!+k@ss?Hk? ^/}iU^̰k5  ޚoiɲ21s 62 S&pM7g 6a}]+&ZĽ_)RSS?`N:"<wQ{mn:oӴ+qR3**`B!S l)f1Yq}[`tAu uwޙE q+n'f33z;xeEW%J4rH @UU6=疿ނS`Yx MQ|bfFJ 6l)kҲgEj}mF޵?W|@`CN>+n9| #<"-i{NYf (Ngsf&1!l1z{ gL9opŕWmrex?i<T2s zv+/_?'9瞓#t`cNjCN`",O|ݓ94]ty킘ќ)`5D? 0;q-~Ol"_{~d+x8-y549:7hͩaaxϱOS~SYYUW\˦R^^θqOns hy\LdΚ IDATöU 4h&wEqWi&{( KZjtz&T\i\8Œ+/28R;6R9v"As:M7Ē%K5j\zIY`^ziVN4իWN 8:f}( !ʨSρQJ!6 PF%AB~b!͛ջ}\ᕅ )lgfPp]L(qo͂|;7n݅ilX.ٻO&>}'Qj͝Ǟ٤ƍ9f1_/[v-:WMV6ej*.&7~?-\_V__Ϭ(//Ok!f*n ֭^YjutxdС6?s0 ^|`˅Ͷ9Wnkw2i$>  ,`̘1ikbRI OBlC)ֳeÆ7)/%񿅵LE$ub5˖-["=zĒH+Xsv'n-֮qKR[.Үm6-^'o}ns'Lmr0MF"v~y/:th|Y>}v4 d ǟ@ L\7n'~59cq%,k)j,:ftiX}Fzg5~#!ޥ*l@uub?c1~l@FUG>Xp\Hw^ ڳZ?"˾?hyy;1!#q8vn6=m.X߁fOkʺ*'q9oyan-)Yb͚5uǍ?.[9[A5a¸qREo֙Lx;[p{%޹n&]0 ՞<穬dcik$D9ՔQ-lE)lC/PL1>|xpĉ'7N PB})aJ) P&RIMQg;0Iݥ}t(Yݪ:̠w{azNaSiHofîfԮݻ76l,aeѢȐ>HKEc9?3?~<&E'_0Y/&OI0ႉ<Ⳝ1yb'9Μ6_7!J6*Ċ aRIwd !t2 g{WaV~>'RHգh.NUŴ}DڒITqP*.\؃C?ȯ&ObAP Κ9^Z*к?w߱qŗ۱ōIx{[:ZxxX ,;n r:Mq`Ĥ#[^Țgjif'-daCU!p=ol9:Ro&5N_qKZ՗_vZHEc!Rkn/{ɧ8Mk|8?h֢X/%`ϒO3gQ[[Gchi0w@  F.]=I\>8Uw Jzk[:4^xQF裏p<ڊY˜RrPER | PC=PF%)g+i ,=OKUB,k}Dd*fE z-^:'Ii:d0svu}_G~W|lf[245snjHRQj-7]zV_[.T=qYLWXǨȿ6TPG7d 'qO[&1od'cFX4p `QГn|I(_Yn}Ԥ6lW'1p !M5J'y2~Λsxɧ|3"s׳Y;|,G gժU;08,h5aƈ#n kFDk +xjf˃sI _T&AkY/ERb|virqRQQoMrꕔq?SEd7|f<2xğ#>N-mo T' z >=38qg.0bС0~qlXF3>b/N6߳tRJJJ{w ]\Y ĉꪫ(,lyÇn:z뭤+VtF3YX˵ #Gi3Yx1ǦM!ȑ#;C0\eK.q??{`+<,϶7 `РAio۹)?MYK/4;ũhwPDң"q/á۬`#?IvQMHƚV0*9jlj52bU"MH+d)Mps [6xoHݘRH5Ȟ6lX>̤h-Y&&^nlvGd&\֯_-۸q#r-*Fk]ǿjafx,Ԫ;Ή:I&m+TA@(rb<"S_rXQTlgAQ7Dtؾ6?2IfIM>&6I&a?G^nݺqȡ#b|+ZRv8GqlM;)vl^US4!iN_A-Gʱ>o9;bw5j>< >(Lwٲe0z̙3c][̜99Lt:tHK l2F&]:f19l,4m5A+ xi{a/MJN,aطOղ*~>gӟ\ng˘xĔ;Ӈw{.:Ɵ1{忯p;q8K{%SXX=V0rF $W]|!q|웯q^.@/R}HmvJ=,XuVˏ(V8|0ޅd1Kg=K&v^|TjYj|Kv=\XwKyi(5w{7{.4iL~BF:x<|_>RK4^}UN9\BF$:_+*6+}vl6 B>͛7G~G?ӟyKkWv52}Hܹsġ+p<[݉xẅ́}}8:-*̲8yxs8/^IbQkn_êkW5O!}(9>yxP4='Co]w+E.e UUٳ'EEE盏;ZEm$GV"|U촥;6IENJ']̿/~?nj:Fw=bەy3zSK=شi558,<̚9gdS>#8Ν;;hq盌׋xO:n ;;93wT>Cv]֣,f1neJܳqۭyF]w}~)~#ZF |lxX-KT6^;:N8ԲhXc1aVs2^Z[D ⑂3%|JSI `Y{w2SάXOM꿆6څ*+QU iwrͷfevߦ]e?!C/6v\l޶݇k6pDkEa՚UуCW_~㤢!Eyf$qTR ^|2-k{BQ.LVӳgOVYņ ѭGVlTTWrgS[ <9z<IfLkVb"Hp߲Ͼr{Mm>xa8Goۧޞc%k5syy#L3sͣ?O0ÏdmNvo qz@Ke\^xi4Ao21T((44lUC^YmQ ͛6mڰkyԽڋ?#ŽnȣB(F׺Ut˚cF#kFfчXnJڵk%cF֢.#" 수2\t:XլHJ,bEU"eWI܍vR(NT 6\RE&3H[/&?z8V1؛x!ӳgOJ~gERP"gPSW˥\itMݫW!Fs~/`9?V"z>l\D]]768 AJ)'7xÍ 'UPmH~ <((hhQ-ڻwo^~e r޹Ò4DҮ];JJJ(--1AO/99SiKz_d'o& dJkL޻Oo$sFwu}?sƯէ%-{I2s_Er]팹>ВKTRsG''|!!:*vSF)农6-KE<=؃_V h\FZS$Gu$?e۶Ɓ(՝;a:toM7Ģ5X̳pk6KM7Ď;xq֋xd|U?՝ZW!{+74XM!z:t`㖍lݺ{ߟ_}9sܹ33^Ѣ $ӟC8 ](3TP&6e|,g8ANb uIt&-C{  (WbCX`jq+VbϸU'dN` Taxl5ԥY,W,Q3kY: ҎBʨZ2)FŞuBŅB""F ^j×ѻF#э3b @9;XB^]AA '6<+bN=z2~ *ֺ̜+"| XXYvlɟ)7|l>LrJ6:PB)ߙ>,A , [>d PS.x8qdAG~  "B!YņK KAAC>6@<." 1[Vў"ECcFũʌ硠ZoӮYK4P ~b g.™Z#QPÛRDN3 IDAT8٣%3;vqӆvP?KbcDeTxJ]Wvxp~K~Q_ AAA|]-kUN]8V~40(0+R‰2XFс6jW y,4EԴש3;68iK!;̖͛NquʅHPI NTT}5;6l O|]AAkt ^ %a&qKBBVJL"76N_JG9U ܝ)0b' Q)fYF68hG!TE} aËJjz:Bn+  BfQD\ VFDVf+>,P؁>?&:$-vA4:naeÆGw_U͝Z ;8;ZUH .SAAUv_ef`2, hm%Xa(YM094܌?=ǖQOj_)63٭ii93MU*vJ(h6+2ou   Nlxln]$>N*QDB [>8SnlĐo" #D?H58qDVW54g9άrFXEQ9;}n~   B@BhGtB5<,0ݱ *XA416U~X' !Lor'XA(ҷ~wQM]lWPC]ʝsR*hKNP.*Mkӎe^   B}q~|vtqDi` yzOT(`i TQL#o71M RrmpX^1 +X_#7r^m   M'>QaLnFK&`E;X=z@%j᠎<\Rv=EaUdٷ|4B"Q>n ɣNTʨj6l'*Zr   M'4̯aIܭGdάpyD#suD*Ɓ*s7̇Ɖ A*SE-~ (jj)֍"aw'9AAAZ=NzBt*)XyXgH2 Lh/ap*j%PHD>SNyqRL>yo`(E3/>vQ)ד   _%JT}A:1Ux80%q"V  @Bma^rO čB"ᣚڬǍ 7wE#:AAAA4U" Q\Յ+>Y"wzsn, 5! .˺N]˧,k;va'HO'Nve|VaGШ؃C7.Pz]QT鎥iCgю" $\6~wh %AAA;Bl8Qpe5E m+M!s`

ֱT ʩ/n]r᠄bSL'҆H:|| b9TW   p0#=q>ۈ_{jjvI~K_\L=Yj`EE!`,$/VDJU*q_PNhhk%jEZw"Zӏ88D[VY@(Ӄ lYmNub% 8gN'Гt#m2_E- ֳ xeAAAA,苋(0( aQB۲bV?A4~} 8".Qlv8SPdS#*Qct XY3N$h\"s/˩%0cOONϛ,nKtb4sR*{RMG%5,g]SOCAAN!6iG }nXQ׍fH9}<\(b;Іq*D *r~zʢ5b^8aV}.bEaƍ3L-k Fa|-օEO0 Ʌh}_XD)AAA>E1PF@DEhY2;=`>TŅN(mD-Ql@kwU,>U2QJt`c*Iۥ-`)OVEI~5lELFroFqx8chG!خʕJ("H   4v:4jMybEd"U 2c?/ Pn8 %%wo3!>XQJ#1"hإeowjnRg ` jVJG2vʨjKgqҎB6Q ̧ZR=_vI(%  вL )@C<"^YR֏하6"Ul_+AˏF);]qp,q A\+cȩ`vD{Vt`'+kmG`w㚚IT1;2pBs-KYMgљv@/lX϶Y9^Hp;[|jɱDAAAh=OΠ AZ=}8$<)S٭euǚ~5񑏍vKss Fj,B͋/Xb+ Va967 ~LJF5@)8MU(u `9?QT '@!/ B1Qބ.0AAz~lQIeVAZ ;wO,%.KX[D Jl;mSA]qY̤dfzw^`Mc5өD:ɜ@q URC>GN PG%5rKa#d99s]    :˜MҬ+,˭㜌euhF68(![k- X1&zRlx󯚞 |)Ånग़ڬTE>.I>ʩʙP&   >:bGsNA6+DC#@|lP膃 ŋ([6 ^?+2}e>Y :VrY&t7WsS)F({"B8Ć;K/Aj d,lU   B3Əծkr( ABanQ,"VVq`Gk/nO%=*Nvw6ǃ=m̆;n]T㧦b    45 _BTcߪJ_a*~D Us1&l LqIܓё}iCgs5[D+J*A4*QO,AAAhat֍C'mi&F 4muOw_5]X`v+`gpbpt6%$[y2e*V;\ #eG@0kwcKbO(hP(I!Q.a  b8#y~I}k.ce VVu2S^)"V։u`Y4U׻Q2Ee2b\d?pR)g#'f}U3O&bY b    n3Lf2ӟλ⏙?xDtSvC4MCUTӭ(7}/E_ 7[Ҿm۶mv+/:d(Nf&G{q֋qcʗ_| uڝ r}N{2wys/K:Fk>Z>,=zp 9xgIkx3Ɲ]Ń`0K\JnPk֬/((cǎ`k&ƍY"Kwׄ'LWf℉]Ldٲe|>N9fϙfs΋ '8#X~=Wf]t|ͦ6k׮܉zj9>rCʊX?Us+'ofcTK@Sc{.y5ƤX'ؖLctjRrNR$H-"`avc-.AAqqU\veߟ<СCP&jQ۾=Jk帊&Y Q(A KA!m_=>Onrc$i m# )++cڮZAܷQo Y UU 9SxWLcΜ13f&a9#۶ms״ڮ\22X ݧ7N9xfsHݠP%/I9~^^|ѣ,1ٯ믣M- )fHO6VJTa;  {4f t:WVeNDO}=i1G't`[9>SGe*rYt5;ׅUUdD44z]\xH+.  8Yp%%%L01cXWmڴgxUee%>(W_媿^ŢQ\\ AƟ1gqgӲشqK>z)Ns"Laax| ==0O~oEصkK,ax駞#ছo(R{xaF\iƟO3^tqv}׵kW{9v;?i.r3m6Sƍ3}z7:6*}\,enOvdm4`>@+Wgq\tEy֙ <`)x~8,=lg4Լ곃c6Z1ceC50z,?сB;\QǷlc=$VfABBV7T7l74X%JAAD((l)5L7*6EBix`s2widnUz BMSAAh <` /EVZŨQشiS¶=zm۶8ڴiøqxꩧ4'|sfscr =;owm|У IDAT\veq7o̱e1ٷ,X۷G~^+|qiUb aF=v_m9տ_t.d1nj:ۏ={zjS|7fCGD\X6m>%/˯wÎ@Q,AAHYd.x<|u ĺup: :>PU_={RYYϴiӟ_\.7p/B8SLᮻoWGh B}e->zJ!6 RkH.X%kWyځQ"vbW^~%O?t\_[|TTTp1qHN[62) qc4g&|{*yS@d8fXU!$EF XJ)Pi :Bev4]eֈK'F1I\Y:v8ƟPAAhvmП &D+'2zh<+3a h/ LwXbP ٗLo&L!QUe #D T)XY Z hа'Y<  seoRU5ZQnwn<qiZ/2n  \x|u]9l BÍ ':{9r)Xۆ ',Fl#eРA|Wq us#af1c֌oT꬈WPmǏ3֤'+D{al3~,f멈ݠ,fډ0Hhkp2+tV٭'G.8p&M ' zKx<{qu?<}[l?f);Xj6lk!/_άY8SٳgNCR㾊uM.J:XKcXXv]+N7gcS}hT5nsaCګ~z4+.g? 3_]v)f(ҟz>HfL"յ~R=uXFv%\˘`˘=+Kӓ+=J5V=RƏPmԦ|"AAvgv;\v _}/cȑwѧOm{1.ΚpwyWF 4tE] M6NOَNI)Os߼>,rX_z9pE[.=|04dy밂VVHGJZ |`<0ZQ\q́<n'T\ة#  Bke蟆ҹsgF=ϊ+ƈ%nJ(/fŚM*x 9fc5LX!H.b`'FYy9=a _~8N^/VO??3L5MK.cчi΢)J]YXWGCXYO^ ǕQĊ]YѶ&`P ;eB"E8"  C= Dl6^{-#FO>r 70dM7| ˣ'!ЌDHRX%U_7XA ?˯,`YB5E%rW X&Uj$aFU4q{4u{ԝ$[&K%K:0^ XT3TA3`TWWbŊHu]M7D~~>tp7owqW]uÇRVV%Kp8݋?. 47A#Uj*d@%tDEOY+YaÎB-A l_OX`Sa $9A{sEP?[0=m 'K/GX('KAҥK6l` ӟD~~>rP^ذax< s=#u~-t{|AN SY1եNMuuci1gq2m۾ ;5J)֓ ciYU,60b[–QĊ`% ZvaU>*b  ԗ"Me<[neǎlڴ){/[nuֱ|H㏧w_9@ABӆgnHMUzBSTd~٩Jn.baÉ-Et_=PÉף4-r/v`= ک0`~fĆ&·(RIم@A![|>Le}W^|7 ڭi <=z_^6[hhWZ 6_ -O}800]a'u?fz ݏv@WVo*DٽD+#j@"~89{:"VX XecxM09 v% B+}P[la˖-Y9ƍٸqc\yؾ}{V#;Ɔ  N` E:4j_1U2#.(@]RSXypc#FYϵ*e;X{в]h74&+DrA+ՅLв~ <"`  x≍z\Gi,Z66BUOn 1 VtݱuO=o67Q;~:sΪT/]8I   6g3n8z^oNYNٯn BG̊J1D-аv(tB S 4;s*]bW~Mp[%ISTTDee%ҥ?ScG#>q~?Κp_5K_lK{T(TԓӋ`j`~9"UYRP"PFd{ & d{~4=.  '$3f`ĉ<39?fcl޼9o!WN#I=q6 ⠒ Yh< _a2&Xmƶ*/))Y38c#WSa&{71MUU7'|7xs X(†lwtF1AC+DraY2cLlM, #A5+/pwGv%w {k4Ǘ dB#%vaI\ojQ'u%.a< \{2!<$ \y8`Nջwo~a~XOɜKr `dLL}l.hbDOhΨ"c5+᳸62|=UlLWqݜrکr-λyC6LJ ?d_Q#B{iBs#,P"[XBJTf7>h=CoDc'q:AAh,d1O?4'N+1xL0GrtA$n%P$%tO.?ԢQn,|)_k(X;>?~t튪C:tL.uqk^ϛ|H_ӷo63Ÿ)n|hlwU2C^lֹ/ӰNX⓮?FF)M#K!TKp4AAݗk'_ˀ?Iee%?|\O<;,8w;v01w\ %_˳9uAh:sX+h#du|bT[V fAKCŭ6T%AAhiTWWsҘ`>ӧOGyo:(&Otz$`u֍ٳg3tP*?==g {$l(^G|}5 6(t@ť'xM ![6LjU}Ig,>mw5kp~v;8<ׯY[Vʖow  ];E,E6Jޫa BLX q!bH{ 3@Ɩ4Fi6QPpvAAhMsq'L:?},"OW\O?ĠA ydzv(쁃r; H\:;~66.˅[PttF7*h|=ǝiTLU;P1S lTMzE 4>;->M(96O,AAձ%,~ISOErF8PiY)EWnĕᮧ aS¦bs,'.eb{R4p|,Rd%pdEf4iHSbT  B᏾*Fؙ`j_XN9^Hkd;u`}I>{&]LV*?0/a-꘨V#۵^?\ ä!?vA~DMe yAA64#"a:T}qVZZiX1 5z_Ǫu`% I0yUWXVJR I>`3d^ʘ*G/򘌓pz_ U^k~b YT9LAA6cU'e "buDν 7VZ`ֱ6'_"'Y+[{Tnݨ4zW2Fg,i$a':442CXܽ7%  +C0gNf}N l߷H'\-XН(vuZĴ@acaz]ʟXqg/'>/Z\gjLt%oЀ1ҌUշiGpI29Bӷ%  cɂNVoѽ"Q HaN=sׂ ~+accZcRTK{>[xeՖO@f*V]>!4Z P$UA8裱'h @P>  'rCuoܹgy͵~ӟKokyy}|+ا È[>=h8BvLPIM$ςoױ2 \.Ⱥ\.S,Jf:nT=Ю PV-^oݶÏݠ/L6JW=-ACEj-eCտ_}o;rLA011 za;۷oPҞm8aw{rJO#~&''9`$T]5nL:[^ת}>ض/y8D4,Q~ IDAT>cP+}USA4."=>??=saJ-hYvA>V~A['S4_V@#%6r0)P䋻J>(.vA%ԃwA߸ 1M]:ok淾T*U{} X153ȘNyoyضmp=ֆ5=o}[Iq/˾bt/X[&mWQkc11,'8Оkʩ:[OT cYj5WU,'xsyc׮]T*ʼn'Dر#9 ZvD죂V?V~Kcd(#VrTYB5gEO'dxAˉr1deAK.vat?z)ms _D׶ZOO':,I?GGct|_2H7Bs!ο";~gmh{Z'nƱdۀDvUȼo 2f&x'8gwpƙg)?{g/|c9{t/sرǞx5߽|_ޙ08á/h9-;ֺZsA2H֚2Lrn*H*_Krkkw"h9'y/k,Z }ŗ\ =w?Cl o|333' pJ_p=ٶC|\~'Fw/uw+dž;̫^ڦcLU[1iozXelRhlXؿ̍\&%GR*~p'yzWu[t]'J< _'4p>םGX5&idn_B`ʕa_`/"{Ew'ª~cgD1:6֛'+ CcA/\?J{LjZ#QXkYFpŮPk|oUjG m7  lt<kqq{﹗b'ww^p[88餓dG,{^ׯ^|G˦TǧImS,,l*7jk[*U@ձ7^t,_<@a <~V kY?;&(u41,+ma tV[X &[ipJMR%6.~&-ncR *6:ά-c  T%~5a?ϼ}%NwoN:W|cz˭ٻ plݻ(J1AX Ҿ1|֛yL.TO LRelRa &`0ɘ\dHQŦ؁d(ܝRy?-ךKK}|3e߾}{˲x%6==__׵וJ\.NRt-k ꈤV>]<0T&%X=@fȑfrm\o?.Sj*X1!Ţ} Z_Q7yYzo㷡:\kk߹; ,\zI{7y͹ =J%#-H=_*ny?Zr13]ȩǽ:%lf1 w+bW-ALOOs^oٲa>o戽mہs'g$G834tUX"eמD$ YmO=&De^Q,ȣG-I¡=Jf_Tݢn]/AAXkq7}Nͩ5ۿ>9:T[]]./क़fwS5DU7S[uMz_#(`5FXm߼ur^ GGS8ȵ6+m;︓^{\#?ۻOivfuu.]2.BvA>gnnG~z_ , ?JJGEC%ҞU) "Dx۳ȫ/['0 a5A ]ZR+ՙnhk%6E^ E(JQtj3lKTfȴ p 'p"Teʵ5MGig6ߟ梋.0 2 bﻟq~l>\[o#>#<7߾CiŦ>G5jA+O/NYiu  2pW-x$$E#T)7EUvZEgvPIP|0Ip l|f=]k&Kj-:Ku'?^*R%ACWbv⾫jsF it)oГ  "F#܉ NbW ~@#F͔#W,*̼kt#65Nϴ@Ej zc:f\FVyԯڢ}瓙[c yLl7d0L#,]eҨX6)bG)" Fc<:醱AlVXƢ rsJ2(iݴwD*lĠRhcGN% ?%Mu3!AE ZE,,lR5!Fg4#+Q:UqQPaF }t~V2)t0EAu@Iq 3UN Tp 8S.`:OU/m۱]Pʑ 2}%ua=0*- .Λ vUt/%S6 aؐdc8Sc/PTR;A+nJaRF9 B WiUzY}d=4L4&0` 9%*k Y|j6SZ`7籰 '10ݨM 'I fi|J0e0zGgRa!Le6X򘌓`R-T f,AS,AAXhl D\ʼnVᖸ3S3kt8LY}I祄$X_k"U*l` pfTwPE~ Z;jZ Z76 x2Y) O%IS[YNzCAh8'|KU"35S|atܴ)-^U+7sZ`;)+Xf&b,S(buA'tzD Z1;v¦rR=B)7j2+j=n-%6I'Yn]LF;덮  Bv̚xy[56}63Kaս5NRZKB pWZh6 mOm;UlR:Ǒ3yt1A&]CSOfKxǶ^Y^Dѐ?R Y44)$ŲA/pԮz_jLPAȹ7,W\J"P_#faS¦PGXR`  ZԷ<;vnZNTem?}>ow&.糝=, xIH$cbaM-`QS7˩mU}Is,)tI|<)uTϳ ^EAuNP̴cڽ`+|^6oo&h]mګ6{^_ wQ8YKYhdǓ{~Ԏ̪]` AYBf,c,He]::,firL,ح A~MH~mVÐr-Q`@nSp*V1 f0y9#O)#9 aY*jmpY3zQqD绹Bâ6z\K VuQTV Y&ƩdrD{Y ޤdeKzM,Bfek> Xapf6Vͷ/LF箈(y"ßsdx9ZҰ~"*h7쯯"VX/-` d8D'X$AAhƇʩd`sBSF Z*evH+`2i4&08!>\<\@_&r”JtRF+pX9F-T᳋Zj<3x>cTj'_-tҋkO5ڂ4DV6(V{_Dc#X؛ZAn{L2bsyQTWX Go"XtTXbpYϊYc~1v[,;aT m~amThJ"Xy>_A Ԃ&L"R~="퍖Q5,,H2~ws %,SJH_ Xa4P枂^{Teɳ@7Y)N,)&:(>١^)zV}A@X"F%kk{Ip3O˰UALi4V)OHRGQE}aAʆq2K ŋ.VYk3:_`EP`PRբǎ!<~+=*LkV X}`DSɲ /cCT׼^ le;&?̟}& I. Z[Pʯ-{nF!Oq:1~V$\b΃+Ȅ)NIB3yfv( ǁ sgL ʔT0Uͫ$.VRA.)ORd-\DRzkd2|e'ZXy~A= U1ԱR&V>N/@a')."% 3U;:4W1LsCd/ \AbU+A1̮yꀰ|vءWaA+/_`?Wpe]$1db.W>WuQ;}{mEm^UP_)_F=4@ж>LЀ=,a'3F ( B"4ODfᴩGN+:*$ءeU…JȨL .=gd--&=63Q (߸`U2?jѫmMzmM^ 5z_q~v(Ġ s|y^e2ʣy{bt8R<4!A9B 0x0qV0e@-]O+UlVCc&ިםJ6>"`eI`/i,[ OQASUx8fJHX/h^eDEW@cä(PeY  &ܛ>%옑I2*-O%5I6w("Vjϲ,kv}`oA6"`%&0AZ@Fg%̶u*ۿVYaA+>ݰ*itV6^ЪbS@#O7"it&",Nx{}~毕W4p#rRE dT!'9Re=ڍZ cDH Y7VZ`T#Ag ! {ǂ t4VLO'fK ata +"bDV֪يe0"X 0X&bdxDwdabZj# ۂO N,ӝQX_ш+2V{껲k 6krQ%yE_*jmk*,0KYrbB¦#Cc%0ȴhp"e,Lit#IY mW*lߣZi|{%4Uu4)!0FOjhT(PEQվ]ۡQ࿪kLc BȢ pIq˫ZO֧>'4@,t`;&i9$"~N^_VƘ}Gw~da W`asUd1 a[aSV(ݾ{ݓ{wlUVLњUvS$p0yR8KnS辩 z/1V/`l7޶ @0K˾sMVD V~ Qe c1I?js"Z_# I!*Cnu_H9d½*JRk]ɺ^K/~ZwŵSasby[&㤙 VV *^uj=׽W8EV0E4[Qe ]^`d{®b3Ge*$ ՙl8¿d*:XkY!nJ-%ϣY]zUq1Y8-ޘ@=k#G S}cI 0K-8}R p+(bwims۹c%ZoI; d]x[uqwzoUQX3d9tWP>.RJ̹uqo~˛ٵkzoN[ ę/pq.+`Νe^sk;{bE,QfyR&ŰTŦD*N tab?f*˔2 m0zecSv뤥%h̐%WH+4Yw(>+:ݰD%'oVg<溊Ղ  9)ª]7"qX`00Mښ}0zᏰAXoN<7|>3p 7/}zobS1ƺ4<,ԑ]vq/W_{޹E,t&^}ucӽb4]VzuC{ypxK#MsYrfFGckaT ^bVe}񽪞Vv:,2A 84p8]X5Ƞ 8JÛTq# X-F2M_yѯpWr/g׮]J%|X,kW7}nmb:[ˍȪ`SV̚ A,b2IU N. 92TYn qSPdIOA69* 0kek*hۺhˡ3XGrdX1Bg r/+ǿV "Rn d2%ˤR)կ~o[o )`  $"#^Lb0L+5CpjDS%\AhRrtV,  B΍^)$N LRP= ¬`1KLX V?r:Z`dt$`W*xU0)Bk F㭑1 b󔺺͂3`[jHI~R>Й 0_ֿ(n V30k#H4e]M @A6>Bq֞I)"BI3*nA44rhhc򆮐'Ff혔}[ ڮMUR[5K |~`! "?"d8UskdǎTJ!f9t`;yll,3ʵ YrX''h5ZlFU\_Ɲt2Gn!):Ό™ԤJ7) &0XbQ1$Uk(:S\ҁIar`%岗_ƍ_3eOs 7rWn;w~(Flj+ ^>1ΞK\]itJaw)Uxp5xFYq&r?gD!thdȢa4T)6X8Ǒ"cAZ~65TKW"X yƷnVW!V cY,~H3BZhZ` äNZ_s~I7̻?Q` AX;rh 3Pj26KX,o ̀@AEm~(UoeWPfa©V ~R;YcJH!M;W208وxm7*o-ri\8=e:ۮF>Xd0KL` :ե1:el橲%RրqJˊ];¢4&0X WaJ+A0A;xKڡذKXT1IK1b̽S@j'ZI-Z .<+ưH*ЯdИ Wvotm$fyoCG#4&aqzCԠh;l.]qE,T؊ o#6N} "X lDKв)P!O!L)uq<&8ӻ.PXšꍳC wደV#,n6Y V7ua1鮋IF6^{8b$q#4H[mc}ن0::&cc ^Q# zo #v+ߞZ6CLjսsʿz{|x5U:_f ȣs+ kGoߡcvsZmxD<)d> ΣʵB^\:.ku X0_&,IunX"AM'Y4&]ᴂдi%-+F*,Qe[t>J 'WݲoW>^-t fL2n4"ILc0OܴAU ~D>(3FRF]*Wr~?P K6K.hU,D(%^hpjLYhޛψmK&X9=V*8ytLW+3 ^{ Ut/ Vo_Pfq FFgM+Uat0jiGrtAK%ʌbȝ}wWWF(K%^AZM ^3,ڏR䂗Ϣ.b8"iw))>^ V:VVd}6p؝p tȨe,vEp+8i$RAhX'TYBä"OJ!xɣ*h- WnX"%n_wNךE :`r  Y7DVZRziʾ&O |}_{8@941v4\eDt:>0N ]gTY` B X[m00Tq}Sdy5by`uN`, &{X:N{߰̏o4KE߯kkr^1 q %naO BwKeF'W|:/_&g>'R7X懬6y y&}Z{8n/N G[<J/D ;ɢ9r輀DQP6p*Ū+j@ ld#}T1VA6[AvgWYfNtf <&?&exW+xw-*y bz5T;A{*al֟LorG(!t4! Yf2F,F ʮ>(q"(pB#18 6pԝ",XEU\)aq".! -"VW9Νiw_(n/`*F޿U˄lS`?rX,mن)dEڣ #Ε  u]Kh7`::SQy,`bMk&vV8a?7Ut(*(v|cQnysutX|4fx%BCٌr[8iz0xA~Dq~g).HyHE]L Uꘛx{-چϠ#dy)gK&/=d]!UQ[v>_~HR t~TQVI4u{4ªrIס0p:Ɖ9nat8iKXcyJ\  &D]a,Cfy!3\ \Ox73EgXc<ȼ+^1Q]e,&@yǠ_Ek4뷳#{B00Y[}\eHs2"` yL^v.X&} TjN6]C09)Ng 6wN94/&/(Q"աZȇpu~!,T$U:4x"5;j7 BuLlX>M&XoEn*L-%TIQQCn+\b2Hֆld*_lbpZJ*nߵ_ {%'1ʭi.g'/f;L"UJTMWz@GGDbq''pr2jD,BX(VheǬ#~VLR+,vmv v(^"ҧ_aѦ`ylL V*V`?jm+AAXO,F 5J'2ȚFewq:yR<>Cܚ9 qQSY~w{{oV ɓ<j?Ƞs9Vvs2h2GyJ'E *+jb!N A X(]?H$WI[q9I~"i0xK)D]yQVVoh`WMEC N$ֹ $er17QV+B޺a% 0Ȉ%kHbQ9INep*-2cO~Ɨy*dUhPLj=0دJ7$ Z~FQA `4'0epqS9gRr Oa/h&[Ŧ^4sǔS(`qAi`Ȯe(a"y (%cqq&,q\BvjsM}$zA!aH:|yN`p:S,Sւcn)BR q :C\.n~l^ Zj;dg4:7va0y'N""%~ZLQ"Dm,lXܔ3^fkd2"(G%Xv"'+[ώYk9E2h@_9("V"|9 O :VAJLvc% " q C$Ǒ繌s7lis??c!&),SզmtC7.W/05Ro/[()3 5SdxYi"R2*/*&Ʋ՚u}%Ɵ3MQ@|U(+UDT⢶z>&;IbU ~b)~ #Ƿ0 XrTR-l`% YKŏ8 8<)~̡Qƫs9cQ`RJR2oWj񪙠i, e 1?u6fs*z^g&ASQX)YЖn"T*R]] U]ART( y4JC긊Ǐرg,cK8q~a\a ύomZRB??Keg+7lec>B x^\DWGV %9L1O'qN2azDe sC(C(2]UγH澙?Mh +glEg颪o~94DtO,qyIIyҳYY~*mVhZ" BV}A$I ;Dg)1:Ӭqu"C4yl6oN"At1N'Kg5*92YZk]CQϲY\}oUmݏ 9qc+ 3IbUͯ긮Zx|󋵟{pea \̲"tɆeLW:c7\Ȫ\򏁶9p|ר1#ț REWq N3WߪTªu^CD  lA2YS Uz8H$ZY:+ur#2ZWy#hg|qcSfv`>J=Eݱ2:忻Rݧ%jmQ"e2]nChq¼QF"Mc!jϐ:(ϯc0b׮ʫXI9ƈ<d/)z!WAҥmLAr11u|h[91&`נھ +_V {%L1D 0N1Q"LX9H3Ӭ3GmaOѤZ/lϳ/#GۃOWj,ԅ>ȰJ$)EhnBUyJs)*j^IeN^9UːRUM image/svg+xml B.S HAPR TRigger On/OFF In Bypass Dry/wet MIDI Trhu Off On BShapr-0.13/makefile000066400000000000000000000150451405711530000143150ustar00rootroot00000000000000SHELL = /bin/sh PKG_CONFIG ?= pkg-config GUI_LIBS += lv2 x11 cairo LV2_LIBS += lv2 ifneq ($(shell $(PKG_CONFIG) --exists fontconfig || echo no), no) GUI_LIBS += fontconfig GUIPPFLAGS += -DPKG_HAVE_FONTCONFIG endif CC ?= gcc CXX ?= g++ INSTALL ?= install INSTALL_PROGRAM ?= $(INSTALL) INSTALL_DATA ?= $(INSTALL) -m644 STRIP ?= strip PREFIX ?= /usr/local LV2DIR ?= $(PREFIX)/lib/lv2 CPPFLAGS += -DPIC CFLAGS += -std=c99 -fvisibility=hidden -fPIC CXXFLAGS += -std=c++11 -fvisibility=hidden -fPIC LDFLAGS += -shared -Wl,-z,relro,-z,now STRIPFLAGS += -s --strip-program=$(STRIP) GUIPPFLAGS += -DPUGL_HAVE_CAIRO DSPCFLAGS += `$(PKG_CONFIG) --cflags $(LV2_LIBS)` GUICFLAGS += `$(PKG_CONFIG) --cflags $(GUI_LIBS)` DSPLFLAGS += `$(PKG_CONFIG) --libs $(LV2_LIBS)` GUILFLAGS += `$(PKG_CONFIG) --libs $(GUI_LIBS)` BUNDLE = BShapr.lv2 DSP = BShapr DSP_SRC = ./src/BShapr.cpp GUI = BShaprGUI GUI_SRC = ./src/BShaprGUI.cpp CV_EXT = -cv OBJ_EXT = .so DSP_OBJ = $(DSP)$(OBJ_EXT) GUI_OBJ = $(GUI)$(OBJ_EXT) DSP_CV_OBJ = $(DSP)$(CV_EXT)$(OBJ_EXT) GUI_CV_OBJ = $(GUI)$(CV_EXT)$(OBJ_EXT) B_OBJECTS = $(addprefix $(BUNDLE)/, $(DSP_OBJ) $(GUI_OBJ) $(DSP_CV_OBJ) $(GUI_CV_OBJ)) ROOTFILES = \ manifest.ttl \ BShapr.ttl \ BShapr-cv.ttl \ LICENSE INCFILES = \ inc/surface.png \ inc/Shape1.png \ inc/Shape2.png \ inc/Shape3.png \ inc/Shape4.png \ inc/Level.png \ inc/Balance.png \ inc/Width.png \ inc/Low_pass.png \ inc/High_pass.png \ inc/Amplify.png \ inc/Low_pass_log.png \ inc/High_pass_log.png \ inc/Pitch_shift.png \ inc/Delay.png \ inc/Doppler_delay.png \ inc/Distortion.png \ inc/Decimate.png \ inc/Bitcrush.png \ inc/Send_midi.png \ inc/Send_cv.png B_FILES = $(addprefix $(BUNDLE)/, $(ROOTFILES) $(INCFILES)) DSP_INCL = src/BUtilities/stof.cpp GUI_CXX_INCL = \ src/MonitorWidget.cpp \ src/SelectWidget.cpp \ src/ValueSelect.cpp \ src/DownClick.cpp \ src/UpClick.cpp \ src/ShapeWidget.cpp \ src/BWidgets/MessageBox.cpp \ src/BWidgets/TextButton.cpp \ src/BWidgets/HPianoRoll.cpp \ src/BWidgets/PianoWidget.cpp \ src/BWidgets/DrawingSurface.cpp \ src/BWidgets/PopupListBox.cpp \ src/BWidgets/ListBox.cpp \ src/BWidgets/ChoiceBox.cpp \ src/BWidgets/ItemBox.cpp \ src/BWidgets/Text.cpp \ src/BWidgets/UpButton.cpp \ src/BWidgets/DownButton.cpp \ src/BWidgets/ToggleButton.cpp \ src/BWidgets/Button.cpp \ src/BWidgets/DialValue.cpp \ src/BWidgets/Dial.cpp \ src/BWidgets/HSwitch.cpp \ src/BWidgets/HSlider.cpp \ src/BWidgets/HScale.cpp \ src/BWidgets/VSwitch.cpp \ src/BWidgets/VSlider.cpp \ src/BWidgets/VScale.cpp \ src/BWidgets/RangeWidget.cpp \ src/BWidgets/ValueWidget.cpp \ src/BWidgets/Knob.cpp \ src/BWidgets/ImageIcon.cpp \ src/BWidgets/Icon.cpp \ src/BWidgets/Label.cpp \ src/BWidgets/Window.cpp \ src/BWidgets/Widget.cpp \ src/BWidgets/BStyles.cpp \ src/BWidgets/BColors.cpp \ src/BWidgets/BItems.cpp \ src/BUtilities/to_string.cpp \ src/BUtilities/stof.cpp GUI_C_INCL = \ src/screen.c \ src/BWidgets/cairoplus.c \ src/BWidgets/pugl/implementation.c \ src/BWidgets/pugl/x11_stub.c \ src/BWidgets/pugl/x11_cairo.c \ src/BWidgets/pugl/x11.c ifeq ($(shell $(PKG_CONFIG) --exists 'lv2 >= 1.12.4' || echo no), no) $(error lv2 >= 1.12.4 not found. Please install lv2 >= 1.12.4 first.) endif ifeq ($(shell $(PKG_CONFIG) --exists 'x11 >= 1.6.0' || echo no), no) $(error x11 >= 1.6.0 not found. Please install x11 >= 1.6.0 first.) endif ifeq ($(shell $(PKG_CONFIG) --exists 'cairo >= 1.12.0' || echo no), no) $(error cairo >= 1.12.0 not found. Please install cairo >= 1.12.0 first.) endif $(BUNDLE): clean $(DSP_OBJ) $(GUI_OBJ) $(DSP_CV_OBJ) $(GUI_CV_OBJ) @cp $(ROOTFILES) $(BUNDLE) @mkdir -p $(BUNDLE)/inc @cp $(INCFILES) $(BUNDLE)/inc all: $(BUNDLE) $(DSP_OBJ): $(DSP_SRC) @echo -n Build $(BUNDLE) DSP... @mkdir -p $(BUNDLE) @$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) $(DSPCFLAGS) -Wl,--start-group $(DSPLFLAGS) $< $(DSP_INCL) -Wl,--end-group -o $(BUNDLE)/$@ @echo \ done. $(GUI_OBJ): $(GUI_SRC) @echo -n Build $(BUNDLE) GUI... @mkdir -p $(BUNDLE)/tmp @cd $(BUNDLE)/tmp; $(CC) $(CPPFLAGS) $(GUIPPFLAGS) $(CFLAGS) $(GUICFLAGS) $(addprefix ../../, $(GUI_C_INCL)) -c @cd $(BUNDLE)/tmp; $(CXX) $(CPPFLAGS) $(GUIPPFLAGS) $(CXXFLAGS) $(GUICFLAGS) $(addprefix ../../, $< $(GUI_CXX_INCL)) -c @$(CXX) $(CPPFLAGS) $(GUIPPFLAGS) $(CXXFLAGS) $(LDFLAGS) $(GUICFLAGS) -Wl,--start-group $(GUILFLAGS) $(BUNDLE)/tmp/*.o -Wl,--end-group -o $(BUNDLE)/$@ @rm -rf $(BUNDLE)/tmp @echo \ done. $(DSP_CV_OBJ): $(DSP_SRC) @echo -n Build $(BUNDLE) \(CV version\) DSP... @mkdir -p $(BUNDLE) @$(CXX) $(CPPFLAGS) -DSUPPORTS_CV $(CXXFLAGS) $(LDFLAGS) $(DSPCFLAGS) -Wl,--start-group $(DSPLFLAGS) $< $(DSP_INCL) -Wl,--end-group -o $(BUNDLE)/$@ @echo \ done. $(GUI_CV_OBJ): $(GUI_SRC) @echo -n Build $(BUNDLE) \(CV version\) GUI... @mkdir -p $(BUNDLE)/tmp_cv @cd $(BUNDLE)/tmp_cv; $(CC) $(CPPFLAGS) -DSUPPORTS_CV $(GUIPPFLAGS) $(CFLAGS) $(GUICFLAGS) $(addprefix ../../, $(GUI_C_INCL)) -c @cd $(BUNDLE)/tmp_cv; $(CXX) $(CPPFLAGS) -DSUPPORTS_CV $(GUIPPFLAGS) $(CXXFLAGS) $(GUICFLAGS) $(addprefix ../../, $< $(GUI_CXX_INCL)) -c @$(CXX) $(CPPFLAGS) -DSUPPORTS_CV $(GUIPPFLAGS) $(CXXFLAGS) $(LDFLAGS) $(GUICFLAGS) -Wl,--start-group $(GUILFLAGS) $(BUNDLE)/tmp_cv/*.o -Wl,--end-group -o $(BUNDLE)/$@ @rm -rf $(BUNDLE)/tmp_cv @echo \ done. install: @echo -n Install $(BUNDLE) to $(DESTDIR)$(LV2DIR)... @$(INSTALL) -d $(DESTDIR)$(LV2DIR)/$(BUNDLE) @$(INSTALL) -d $(DESTDIR)$(LV2DIR)/$(BUNDLE)/inc @$(INSTALL_PROGRAM) -m755 $(B_OBJECTS) $(DESTDIR)$(LV2DIR)/$(BUNDLE) @$(INSTALL_DATA) $(addprefix $(BUNDLE)/, $(ROOTFILES)) $(DESTDIR)$(LV2DIR)/$(BUNDLE) @$(INSTALL_DATA) $(addprefix $(BUNDLE)/, $(INCFILES)) $(DESTDIR)$(LV2DIR)/$(BUNDLE)/inc @echo \ done. install-strip: @echo -n "Install (stripped)" $(BUNDLE) to $(DESTDIR)$(LV2DIR)... @$(INSTALL) -d $(DESTDIR)$(LV2DIR)/$(BUNDLE) @$(INSTALL) -d $(DESTDIR)$(LV2DIR)/$(BUNDLE)/inc @$(INSTALL_PROGRAM) -m755 $(STRIPFLAGS) $(B_OBJECTS) $(DESTDIR)$(LV2DIR)/$(BUNDLE) @$(INSTALL_DATA) $(addprefix $(BUNDLE)/, $(ROOTFILES)) $(DESTDIR)$(LV2DIR)/$(BUNDLE) @$(INSTALL_DATA) $(addprefix $(BUNDLE)/, $(INCFILES)) $(DESTDIR)$(LV2DIR)/$(BUNDLE)/inc @echo \ done. uninstall: @echo -n Uninstall $(BUNDLE)... @rm -f $(addprefix $(DESTDIR)$(LV2DIR)/$(BUNDLE)/, $(ROOTFILES) $(INCFILES)) -@rmdir $(DESTDIR)$(LV2DIR)/$(BUNDLE)/inc @rm -f $(DESTDIR)$(LV2DIR)/$(BUNDLE)/$(GUI_OBJ) @rm -f $(DESTDIR)$(LV2DIR)/$(BUNDLE)/$(DSP_OBJ) @rm -f $(DESTDIR)$(LV2DIR)/$(BUNDLE)/$(GUI_CV_OBJ) @rm -f $(DESTDIR)$(LV2DIR)/$(BUNDLE)/$(DSP_CV_OBJ) -@rmdir $(DESTDIR)$(LV2DIR)/$(BUNDLE) @echo \ done. clean: @rm -rf $(BUNDLE) .PHONY: all install install-strip uninstall clean .NOTPARALLEL: BShapr-0.13/manifest.ttl000066400000000000000000000013011405711530000151360ustar00rootroot00000000000000@prefix lv2: . @prefix rdfs: . @prefix ui: . a lv2:Plugin ; lv2:binary ; rdfs:seeAlso . a ui:X11UI; ui:binary ; rdfs:seeAlso . a lv2:Plugin ; lv2:binary ; rdfs:seeAlso . a ui:X11UI; ui:binary ; rdfs:seeAlso . BShapr-0.13/src/000077500000000000000000000000001405711530000133775ustar00rootroot00000000000000BShapr-0.13/src/BShapr.cpp000066400000000000000000001446611405711530000152760ustar00rootroot00000000000000/* B.Shapr * Beat / envelope shaper LV2 plugin * * Copyright (C) 2019 by Sven Jähnichen * * This program 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, or (at your option) * any later version. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include "BShapr.hpp" #include "BUtilities/stof.hpp" #define LIM(g , min, max) ((g) > (max) ? (max) : ((g) < (min) ? (min) : (g))) #define SGN(a) (((a) > 0) - ((a) < 0)) #define SQR(a) ((a) * (a)) inline float db2co (const float value) {return pow (10, 0.05 * value);} inline double floorfrac (const double value) {return value - floor (value);} AudioBuffer::AudioBuffer () : AudioBuffer (0) {} AudioBuffer::AudioBuffer (const uint32_t size) : frames (nullptr), wPtr1 (0), wPtr2 (0), rPtr1 (0), rPtr2 (0), size (0) { if (size !=0) { try {resize (size);} catch (std::bad_alloc& ba) {throw ba;} } } AudioBuffer::~AudioBuffer () { if (frames) delete[] (frames); } void AudioBuffer::resize (const uint32_t size) { if (frames) delete[] (frames); frames = nullptr; try {frames = new float[size];} catch (std::bad_alloc& ba) { this->size = 0; throw ba; } this->size = size; } void AudioBuffer::reset () { if (frames) { memset (frames, 0, size * sizeof (float)); memset (frames, 0, size * sizeof (float)); wPtr1 = wPtr2 = rPtr1 = rPtr2 = 0; } } Message::Message () : messageBits (0), scheduled (true) {} void Message::clearMessages () { messageBits = 0; scheduled = true; } void Message::setMessage (MessageNr messageNr) { if ((messageNr > NO_MSG) && (messageNr <= MAX_MSG) && (!isMessage (messageNr))) { messageBits = messageBits | (1 << (messageNr - 1)); scheduled = true; } } void Message::deleteMessage (MessageNr messageNr) { if ((messageNr > NO_MSG) && (messageNr <= MAX_MSG) && (isMessage (messageNr))) { messageBits = messageBits & (~(1 << (messageNr - 1))); scheduled = true; } } bool Message::isMessage (MessageNr messageNr) { if ((messageNr > NO_MSG) && (messageNr <= MAX_MSG)) return ((messageBits & (1 << (messageNr - 1))) != 0); else if (messageNr == NO_MSG) return (messageBits == 0); else return false; } MessageNr Message::loadMessage () { scheduled = false; for (int i = NO_MSG + 1; i <= MAX_MSG; ++i) { MessageNr messageNr = MessageNr (i); if (isMessage (messageNr)) return messageNr; } return NO_MSG; } bool Message::isScheduled () {return scheduled;} Fader::Fader () : Fader (0, 1) {} Fader::Fader (const float value, const float speed) : value (value), target (value), speed (speed) {} inline void Fader::setTarget (const float target) {this->target = target;} inline void Fader::setSpeed (const float speed) {this->speed = speed;} inline float Fader::proceed () { if (fabsf (target - value) < speed) value = target; else value += SGN (target - value) * speed; return value; } inline float Fader::getValue () const {return value;} BShapr::BShapr (double samplerate, const LV2_Feature* const* features) : map(NULL), rate(samplerate), bpm(120.0f), speed(1), bar (0), barBeat (0), beatsPerBar (4), beatUnit (4), position(0), offset(0), refFrame(0), audioInput1(NULL), audioInput2(NULL), audioOutput1(NULL), audioOutput2(NULL), new_controllers {NULL}, controllers {0}, shapes {Shape ()}, tempNodes {StaticArrayList ()}, urids (), controlPort(NULL), notifyPort(NULL), #ifdef SUPPORTS_CV cvOutputs {NULL}, #endif forge (), notify_frame (), key (0xFF), ui_on(false), message (), monitorPos(-1), notificationsCount(0), stepCount (0), scheduleNotifyStatus (true) { std::fill (sendValue, sendValue + MAXSHAPES, 0xFF); std::fill (factors, factors + MAXSHAPES, Fader (0, 1.0f / (0.02f * rate))); for (int i = 0; i < MAXSHAPES; ++i) { shapes[i].setDefaultShape (); shapes[i].setTransformation (methods[0].transformFactor, methods[0].transformOffset); try {audioBuffer1[i].resize (samplerate);} catch (std::bad_alloc& ba) {throw ba;} try {audioBuffer2[i].resize (samplerate);} catch (std::bad_alloc& ba) {throw ba;} } notifications.fill ({0.0f, {0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}}); fillFilterBuffer (filter1Buffer1, 0); fillFilterBuffer (filter1Buffer2, 0); fillFilterBuffer (filter2Buffer1, 0); fillFilterBuffer (filter2Buffer2, 0); //Scan host features for URID map LV2_URID_Map* m = NULL; for (int i = 0; features[i]; ++i) { if (strcmp(features[i]->URI, LV2_URID__map) == 0) { m = (LV2_URID_Map*) features[i]->data; } } if (!m) throw std::invalid_argument ("Host does not support urid:map"); //Map URIS map = m; mapURIDs (m, &urids); // Initialize forge lv2_atom_forge_init (&forge, map); for (int i = 0; i < MAXSHAPES; ++i) scheduleNotifyShapes[i] = true; } BShapr::~BShapr () {} void BShapr::connect_port(uint32_t port, void *data) { switch (port) { case CONTROL: controlPort = (LV2_Atom_Sequence*) data; break; case NOTIFY: notifyPort = (LV2_Atom_Sequence*) data; break; case AUDIO_IN_1: audioInput1 = (float*) data; break; case AUDIO_IN_2: audioInput2 = (float*) data; break; case AUDIO_OUT_1: audioOutput1 = (float*) data; break; case AUDIO_OUT_2: audioOutput2 = (float*) data; break; default: #ifdef SUPPORTS_CV if ((port >= CV_OUT) && (port < CV_OUT + MAXSHAPES)) cvOutputs[port - CV_OUT] = (float*) data; #endif if ((port >= CONTROLLERS) && (port < CONTROLLERS + NR_CONTROLLERS)) new_controllers[port - CONTROLLERS] = (float*) data; } } void BShapr::fillFilterBuffer (float filterBuffer[MAXSHAPES] [MAX_F_ORDER / 2], const float value) { for (int i = 0; i < MAXSHAPES; ++ i) { for (int j = 0; j < MAX_F_ORDER / 2; ++ j) { filterBuffer[i][j] = value; } } } bool BShapr::isAudioOutputConnected (int shapeNr) { if (controllers[SHAPERS + shapeNr * SH_SIZE + SH_OUTPUT] != 0) return true; for (int i = shapeNr + 1; i < MAXSHAPES; ++i) { bool status = false; if (controllers[SHAPERS + i * SH_SIZE + SH_INPUT] == shapeNr + 3) status = isAudioOutputConnected (i); if (status) {return true;} } return false; } double BShapr::getPositionFromBeats (double beats) { if (controllers[BASE_VALUE] == 0.0) return 0.0; switch (int (controllers[BASE])) { case SECONDS: return (bpm ? beats / (controllers[BASE_VALUE] * (bpm / 60.0)) : 0.0); case BEATS: return beats / controllers[BASE_VALUE]; case BARS: return (beatsPerBar ? beats / (controllers[BASE_VALUE] * beatsPerBar) : 0.0); default: return 0.0; } } double BShapr::getPositionFromFrames (uint64_t frames) { if ((controllers[BASE_VALUE] == 0.0) || (rate == 0)) return 0.0; switch (int (controllers[BASE])) { case SECONDS: return frames * (1.0 / rate) / controllers[BASE_VALUE] ; case BEATS: return (bpm ? frames * (speed / (rate / (bpm / 60))) / controllers[BASE_VALUE] : 0.0); case BARS: return (bpm && beatsPerBar ? frames * (speed / (rate / (bpm / 60))) / (controllers[BASE_VALUE] * beatsPerBar) : 0.0); default: return 0.0; } } double BShapr::getPositionFromSeconds (double seconds) { if (controllers[BASE_VALUE] == 0.0) return 0.0; switch (int (controllers[BASE])) { case SECONDS : return seconds / controllers[BASE_VALUE]; case BEATS: return seconds * (bpm / 60.0) / controllers[BASE_VALUE]; case BARS: return (beatsPerBar ? seconds * (bpm / 60.0 / beatsPerBar) / controllers[BASE_VALUE] : 0.0); default: return 0; } } void BShapr::run (uint32_t n_samples) { // Check ports if ((!controlPort) || (!notifyPort) || (!audioInput1) || (!audioInput2) || (!audioOutput1) || (!audioOutput2)) return; for (int i = 0; i < NR_CONTROLLERS; ++i) if (!new_controllers[i]) return; // Prepare forge buffer and initialize atom sequence const uint32_t space = notifyPort->atom.size; lv2_atom_forge_set_buffer(&forge, (uint8_t*) notifyPort, space); lv2_atom_forge_sequence_head(&forge, ¬ify_frame, 0); // Update controller values for (int i = 0; i < NR_CONTROLLERS; ++i) { if (controllers[i] != *new_controllers[i]) { float newValue = *new_controllers[i]; int shapeNr = ((i >= SHAPERS) ? ((i - SHAPERS) / SH_SIZE) : -1); int shapeControllerNr = ((i >= SHAPERS) ? ((i - SHAPERS) % SH_SIZE) : -1); // Global controllers if (i < SHAPERS) { newValue = globalControllerLimits[i].validate (newValue); if (i == MIDI_CONTROL) { if (newValue == 0.0f) { // Hard set position back to offset-independent position position = floorfrac (position + offset); offset = 0; } else key = 0xFF; } else if (i == BASE) { if (newValue == SECONDS) { if (bpm < 1.0) message.setMessage (JACK_STOP_MSG); else message.deleteMessage (JACK_STOP_MSG); } else { if ((speed == 0) || (bpm < 1.0)) message.setMessage (JACK_STOP_MSG); else message.deleteMessage (JACK_STOP_MSG); } } } // Shape controllers else { newValue = shapeControllerLimits[shapeControllerNr].validate (newValue); // Target if (shapeControllerNr == SH_TARGET) { // Change transformation shapes[shapeNr].setTransformation (methods[int(newValue)].transformFactor, methods[int(newValue)].transformOffset); const float sm = controllers[SHAPERS + shapeNr * SH_SIZE + SH_SMOOTHING]; factors[shapeNr] = Fader ( methods[int(newValue)].transformOffset, methods[int(newValue)].step / (0.001f * sm * rate) ); // Clear audiobuffers, if needed if ( (newValue == BShaprTargetIndex::PITCH) || (newValue == BShaprTargetIndex::DELAY) || (newValue == BShaprTargetIndex::DOPPLER) ) { audioBuffer1[shapeNr].reset (); audioBuffer2[shapeNr].reset (); } #ifndef SUPPORTS_CV // Force update & send MIDI if switched to MIDI else if (newValue == BShaprTargetIndex::SEND_MIDI) sendValue[shapeNr] = 0xff; #endif } else if (shapeControllerNr == SH_SMOOTHING) { const int me = controllers[SHAPERS + shapeNr * SH_SIZE + SH_TARGET]; factors[shapeNr].setSpeed (methods[me].step/ (0.001f * newValue * rate)); } // Options else if ((shapeControllerNr >= SH_OPTION) && (shapeControllerNr < SH_OPTION + MAXOPTIONS)) { int optionNr = shapeControllerNr - SH_OPTION; newValue = options[optionNr].limit.validate (newValue); // Force update & send MIDI if parameter changed if ((optionNr == SEND_MIDI_CH) || (optionNr == SEND_MIDI_CC)) sendValue[shapeNr] = 0xff; } } controllers[i] = newValue; } } // Check for waiting tempNodes for (int i = 0; i < MAXSHAPES; ++i) { while (!tempNodes[i].empty()) { Node n = tempNodes[i].back(); shapes[i].insertNode (n); tempNodes[i].pop_back(); } } // Check activeShape input int activeShape = LIM (controllers[ACTIVE_SHAPE], 1, MAXSHAPES) - 1; if (controllers[SHAPERS + activeShape * SH_SIZE + SH_INPUT] == 0) message.setMessage (NO_INPUT_MSG); else message.deleteMessage (NO_INPUT_MSG); // Check activeShape output if (!isAudioOutputConnected (activeShape)) message.setMessage (NO_OUTPUT_MSG); else message.deleteMessage (NO_OUTPUT_MSG); // Control and MIDI messages uint32_t last_t = 0; LV2_ATOM_SEQUENCE_FOREACH(controlPort, ev) { // Read host & GUI events if ((ev->body.type == urids.atom_Object) || (ev->body.type == urids.atom_Blank)) { const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; // Process GUI on status data if (obj->body.otype == urids.ui_on) { ui_on = true; for (int i = 0; i < MAXSHAPES; ++i) scheduleNotifyShapes[i] = true; } // Process GUI off status data else if (obj->body.otype == urids.ui_off) ui_on = false; // Process (full) shape data else if (obj->body.otype == urids.notify_shapeEvent) { LV2_Atom *sNr = NULL, *sData = NULL; lv2_atom_object_get ( obj, urids.notify_shapeNr, &sNr, urids.notify_shapeData, &sData, NULL ); if (sNr && (sNr->type == urids.atom_Int) && sData && (sData->type == urids.atom_Vector)) { int shapeNr = ((LV2_Atom_Int*)sNr)->body; if ((shapeNr >= 0) && (shapeNr < MAXSHAPES)) { const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*) sData; size_t vecSize = (sData->size - sizeof(LV2_Atom_Vector_Body)) / (7 * sizeof (float)); if (vec->body.child_type == urids.atom_Float) { shapes[shapeNr].clearShape (); float* data = (float*)(&vec->body + 1); for (unsigned int nodeNr = 0; (nodeNr < vecSize) && (nodeNr < MAXNODES); ++nodeNr) { Node node (&data[nodeNr * 7]); shapes[shapeNr].appendRawNode (node); } shapes[shapeNr].validateShape(); } } } } // Process time / position data else if (obj->body.otype == urids.time_Position) { bool scheduleUpdatePosition = false; // Update bpm, speed, position LV2_Atom *oBbeat = NULL, *oBpm = NULL, *oSpeed = NULL, *oBpb = NULL, *oBu = NULL, *oBar = NULL; const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body; lv2_atom_object_get ( obj, urids.time_bar, &oBar, urids.time_barBeat, &oBbeat, urids.time_beatsPerMinute, &oBpm, urids.time_beatsPerBar, &oBpb, urids.time_beatUnit, &oBu, urids.time_speed, &oSpeed, NULL ); // BPM changed? if (oBpm && (oBpm->type == urids.atom_Float)) { float nbpm = ((LV2_Atom_Float*)oBpm)->body; if (nbpm != bpm) { bpm = nbpm; if (nbpm < 1.0) { message.setMessage (JACK_STOP_MSG); fillFilterBuffer (filter1Buffer1, 0); fillFilterBuffer (filter1Buffer2, 0); fillFilterBuffer (filter2Buffer1, 0); fillFilterBuffer (filter2Buffer2, 0); } else message.deleteMessage (JACK_STOP_MSG); } } // Beats per bar changed? if (oBpb && (oBpb->type == urids.atom_Float) && (((LV2_Atom_Float*)oBpb)->body > 0)) { beatsPerBar = ((LV2_Atom_Float*)oBpb)->body; scheduleNotifyStatus = true; } // BeatUnit changed? if (oBu && (oBu->type == urids.atom_Int) && (((LV2_Atom_Int*)oBu)->body > 0)) { beatUnit = ((LV2_Atom_Int*)oBu)->body; scheduleNotifyStatus = true; } // Speed changed? if (oSpeed && (oSpeed->type == urids.atom_Float)) { float nspeed = ((LV2_Atom_Float*)oSpeed)->body; if (nspeed != speed) { if (controllers[BASE] != SECONDS) { // Started ? if (speed == 0) { for (int i = 0; i < MAXSHAPES; ++i) { audioBuffer1[i].reset (); audioBuffer2[i].reset (); } } // Stopped ? if (nspeed == 0) { message.setMessage (JACK_STOP_MSG); fillFilterBuffer (filter1Buffer1, 0); fillFilterBuffer (filter1Buffer2, 0); fillFilterBuffer (filter2Buffer1, 0); fillFilterBuffer (filter2Buffer2, 0); } // Not stopped ? else message.deleteMessage (JACK_STOP_MSG); } speed = nspeed; } } // Bar position changed if (oBar && (oBar->type == urids.atom_Long) && (bar != ((uint64_t)((LV2_Atom_Long*)oBar)->body))) { bar = ((LV2_Atom_Long*)oBar)->body; scheduleUpdatePosition = true; } // Beat position changed (during playing) ? if (oBbeat && (oBbeat->type == urids.atom_Float)) { barBeat = ((LV2_Atom_Float*)oBbeat)->body; scheduleUpdatePosition = true; } if (scheduleUpdatePosition) { // Hard set new position if new data received double pos = getPositionFromBeats (barBeat + beatsPerBar * bar); position = floorfrac (pos - offset); refFrame = ev->time.frames; } } } // Read incoming MIDI events if (ev->body.type == urids.midi_Event) { const uint8_t* const msg = (const uint8_t*)(ev + 1); // Forward MIDI event if (controllers[MIDI_THRU] != 0.0f) { LV2_Atom midiatom; midiatom.type = urids.midi_Event; midiatom.size = ev->body.size; lv2_atom_forge_frame_time (&forge, ev->time.frames); lv2_atom_forge_raw (&forge, &midiatom, sizeof (LV2_Atom)); lv2_atom_forge_raw (&forge, msg, midiatom.size); lv2_atom_forge_pad (&forge, sizeof (LV2_Atom) + midiatom.size); } // Analyze MIDI event if (controllers[MIDI_CONTROL] == 1.0f) { uint8_t typ = lv2_midi_message_type(msg); // uint8_t chn = msg[0] & 0x0F; uint8_t note = msg[1]; uint32_t filter = controllers[MIDI_KEYS]; switch (typ) { case LV2_MIDI_MSG_NOTE_ON: { if (filter & (1 << (note % 12))) { key = note; offset = floorfrac (position + offset); position = 0; refFrame = ev->time.frames; } } break; case LV2_MIDI_MSG_NOTE_OFF: { if (key == note) { key = 0xFF; } } break; case LV2_MIDI_MSG_CONTROLLER: { if ((note == LV2_MIDI_CTL_ALL_NOTES_OFF) || (note == LV2_MIDI_CTL_ALL_SOUNDS_OFF)) { key = 0xFF; } } break; default: break; } } } uint32_t next_t = (ev->time.frames < n_samples ? ev->time.frames : n_samples); play (last_t, next_t); last_t = next_t; } // Play remaining samples if (last_t < n_samples) play (last_t, n_samples); // Update position in case of no new barBeat submitted on next call double relpos = getPositionFromFrames (n_samples - refFrame); // Position relative to reference frame position = floorfrac (position + relpos); refFrame = 0; // Send collected data to GUI if (ui_on) { notifyMonitorToGui (); for (int i = 0; i < MAXSHAPES; ++i) if (scheduleNotifyShapes[i]) notifyShapeToGui (i); if (message.isScheduled ()) notifyMessageToGui (); if (scheduleNotifyStatus) notifyStatusToGui (); } lv2_atom_forge_pop (&forge, ¬ify_frame); } void BShapr::notifyMonitorToGui() { if (notificationsCount > 0) { if (notificationsCount > NOTIFYBUFFERSIZE) notificationsCount = NOTIFYBUFFERSIZE; LV2_Atom_Forge_Frame frame; lv2_atom_forge_frame_time(&forge, 0); lv2_atom_forge_object(&forge, &frame, 0, urids.notify_monitorEvent); lv2_atom_forge_key(&forge, urids.notify_monitor); lv2_atom_forge_vector(&forge, sizeof(float), urids.atom_Float, (uint32_t) (9 * notificationsCount), ¬ifications); lv2_atom_forge_pop(&forge, &frame); memset (¬ifications, 0, notificationsCount * sizeof (BShaprNotifications)); notificationsCount = 0; stepCount = 0; } } void BShapr::notifyShapeToGui (int shapeNr) { size_t size = shapes[shapeNr].size (); // Load shapeBuffer for (unsigned int i = 0; i < size; ++i) { Node node = shapes[shapeNr].getRawNode (i); shapeBuffer[i * 7] = (float)node.nodeType; shapeBuffer[i * 7 + 1] = (float)node.point.x; shapeBuffer[i * 7 + 2] = (float)node.point.y; shapeBuffer[i * 7 + 3] = (float)node.handle1.x; shapeBuffer[i * 7 + 4] = (float)node.handle1.y; shapeBuffer[i * 7 + 5] = (float)node.handle2.x; shapeBuffer[i * 7 + 6] = (float)node.handle2.y; } // Notify shapeBuffer LV2_Atom_Forge_Frame frame; lv2_atom_forge_frame_time(&forge, 0); lv2_atom_forge_object(&forge, &frame, 0, urids.notify_shapeEvent); lv2_atom_forge_key(&forge, urids.notify_shapeNr); lv2_atom_forge_int(&forge, shapeNr); lv2_atom_forge_key(&forge, urids.notify_shapeData); lv2_atom_forge_vector(&forge, sizeof(float), urids.atom_Float, (uint32_t) (7 * size), &shapeBuffer); lv2_atom_forge_pop(&forge, &frame); scheduleNotifyShapes[shapeNr] = false; } void BShapr::notifyMessageToGui() { uint32_t messageNr = message.loadMessage (); // Send notifications LV2_Atom_Forge_Frame frame; lv2_atom_forge_frame_time(&forge, 0); lv2_atom_forge_object(&forge, &frame, 0, urids.notify_messageEvent); lv2_atom_forge_key(&forge, urids.notify_message); lv2_atom_forge_int(&forge, messageNr); lv2_atom_forge_pop(&forge, &frame); } void BShapr::notifyStatusToGui() { // Send notifications LV2_Atom_Forge_Frame frame; lv2_atom_forge_frame_time(&forge, 0); lv2_atom_forge_object(&forge, &frame, 0, urids.notify_statusEvent); lv2_atom_forge_key(&forge, urids.time_beatsPerBar); lv2_atom_forge_float(&forge, beatsPerBar); lv2_atom_forge_key(&forge, urids.time_beatUnit); lv2_atom_forge_int(&forge, beatUnit); lv2_atom_forge_key(&forge, urids.time_beatsPerMinute); lv2_atom_forge_float(&forge, bpm); lv2_atom_forge_pop(&forge, &frame); scheduleNotifyStatus = false; } void BShapr::audioLevel (const float input1, const float input2, float* output1, float* output2, const float amp) { *output1 = input1 * LIM (amp, methods[LEVEL].limit.min, methods[LEVEL].limit.max); *output2 = input2 * LIM (amp, methods[LEVEL].limit.min, methods[LEVEL].limit.max); } void BShapr::stereoBalance (const float input1, const float input2, float* output1, float* output2, const float balance) { float f = LIM (balance, methods[BALANCE].limit.min, methods[BALANCE].limit.max); if (f < 0) { *output1 = input1 + (0 - f) * input2; *output2 = (f + 1) * input2; } else { *output1 = (1 - f) * input1; *output2 = input2 + f * input1; } } void BShapr::stereoWidth (const float input1, const float input2, float* output1, float* output2, const float width) { float f = LIM (width, methods[WIDTH].limit.min, methods[WIDTH].limit.max); float m = (input1 + input2) / 2; float s = (input1 - input2) * f / 2; *output1 = m + s; *output2 = m - s; } // Butterworth algorithm void BShapr::lowPassFilter (const float input1, const float input2, float* output1, float* output2, const float cutoffFreq, const int shape) { int order = controllers[SHAPERS + shape * SH_SIZE + SH_OPTION + DB_PER_OCT_OPT] / 6; float f = LIM (cutoffFreq, methods[LOW_PASS].limit.min, methods[LOW_PASS].limit.max); float a = tan (M_PI * f / rate); float a2 = a * a; float coeff0 [MAX_F_ORDER / 2]; float coeff1 [MAX_F_ORDER / 2]; float coeff2 [MAX_F_ORDER / 2]; float filter1Buffer0 [MAX_F_ORDER / 2]; float filter2Buffer0 [MAX_F_ORDER / 2]; for (int i = 0; i < int (order / 2); ++i) { float r = sin (M_PI * (2.0f * i + 1.0f) / (2.0f * order)); float s = a2 + 2.0f * a * r + 1.0f; coeff0[i] = a2 / s; coeff1[i] = 2.0f * (1 - a2) / s; coeff2[i] = -(a2 - 2.0f * a * r + 1.0f) / s; } double f1 = input1; double f2 = input2; for (int i = 0; i < int (order / 2); ++i) { filter1Buffer0[i] = coeff1[i] * filter1Buffer1[shape][i] + coeff2[i] * filter1Buffer2[shape][i] + f1; filter2Buffer0[i] = coeff1[i] * filter2Buffer1[shape][i] + coeff2[i] * filter2Buffer2[shape][i] + f2; f1 = coeff0[i] * (filter1Buffer0[i] + 2.0f * filter1Buffer1[shape][i] + filter1Buffer2[shape][i]); f2 = coeff0[i] * (filter2Buffer0[i] + 2.0f * filter2Buffer1[shape][i] + filter2Buffer2[shape][i]); filter1Buffer2[shape][i] = filter1Buffer1[shape][i]; filter1Buffer1[shape][i] = filter1Buffer0[i]; filter2Buffer2[shape][i] = filter2Buffer1[shape][i]; filter2Buffer1[shape][i] = filter2Buffer0[i]; } *output1 = f1; *output2 = f2; } // Butterworth algorithm void BShapr::highPassFilter (const float input1, const float input2, float* output1, float* output2, const float cutoffFreq, const int shape) { int order = controllers[SHAPERS + shape * SH_SIZE + SH_OPTION + DB_PER_OCT_OPT] / 6; float f = LIM (cutoffFreq, methods[HIGH_PASS].limit.min, methods[HIGH_PASS].limit.max); float a = tan (M_PI * f / rate); float a2 = a * a; float coeff0 [MAX_F_ORDER / 2]; float coeff1 [MAX_F_ORDER / 2]; float coeff2 [MAX_F_ORDER / 2]; float filter1Buffer0 [MAX_F_ORDER / 2]; float filter2Buffer0 [MAX_F_ORDER / 2]; for (int i = 0; i < int (order / 2); ++i) { float r = sin (M_PI * (2.0f * i + 1.0f) / (2.0f * order)); float s = a2 + 2.0f * a * r + 1.0f; coeff0[i] = 1 / s; coeff1[i] = 2.0f * (1 - a2) / s; coeff2[i] = -(a2 - 2.0f * a * r + 1.0f) / s; } double f1 = input1; double f2 = input2; for (int i = 0; i < int (order / 2); ++i) { filter1Buffer0[i] = coeff1[i] * filter1Buffer1[shape][i] + coeff2[i] * filter1Buffer2[shape][i] + f1; filter2Buffer0[i] = coeff1[i] * filter2Buffer1[shape][i] + coeff2[i] * filter2Buffer2[shape][i] + f2; f1 = coeff0[i] * (filter1Buffer0[i] - 2.0f * filter1Buffer1[shape][i] + filter1Buffer2[shape][i]); f2 = coeff0[i] * (filter2Buffer0[i] - 2.0f * filter2Buffer1[shape][i] + filter2Buffer2[shape][i]); filter1Buffer2[shape][i] = filter1Buffer1[shape][i]; filter1Buffer1[shape][i] = filter1Buffer0[i]; filter2Buffer2[shape][i] = filter2Buffer1[shape][i]; filter2Buffer1[shape][i] = filter2Buffer0[i]; } *output1 = f1; *output2 = f2; } // Ring buffer method with least squares ring closure void BShapr::pitch (const float input1, const float input2, float* output1, float* output2, const float semitone, const int shape) { const int pitchBufferSize = rate * PITCHBUFFERTIME / 1000; const int pitchFaderSize = rate * PITCHFADERTIME / 1000; const float p = LIM (semitone, methods[PITCH].limit.min, methods[PITCH].limit.max); const double pitchFactor = pow (2, p / 12); const uint32_t wPtr = audioBuffer1[shape].wPtr1; const double rPtr = audioBuffer1[shape].rPtr1; const uint32_t rPtrInt = uint32_t (rPtr); const double rPtrFrac = fmod (rPtr, 1); double diff = rPtr - wPtr; if (diff > pitchBufferSize / 2) diff = diff - pitchBufferSize; if (diff < -pitchBufferSize / 2) diff = diff + pitchBufferSize; // Write to buffers and output audioBuffer1[shape].frames[wPtr % pitchBufferSize] = input1; audioBuffer2[shape].frames[wPtr % pitchBufferSize] = input2; *output1 = (1 - rPtrFrac) * audioBuffer1[shape].frames[rPtrInt % pitchBufferSize] + rPtrFrac * audioBuffer1[shape].frames[(rPtrInt + 1) % pitchBufferSize]; *output2 = (1 - rPtrFrac) * audioBuffer2[shape].frames[rPtrInt % pitchBufferSize] + rPtrFrac * audioBuffer2[shape].frames[(rPtrInt + 1) % pitchBufferSize]; // Update pointers const double newWPtr = (wPtr + 1) % pitchBufferSize; double newRPtr = fmod (rPtr + pitchFactor, pitchBufferSize); double newDiff = newRPtr - newWPtr; if (newDiff > pitchBufferSize / 2) newDiff = newDiff - pitchBufferSize; if (newDiff < -pitchBufferSize / 2) newDiff = newDiff + pitchBufferSize; // Run into new data area on positive pitch or // run into old data area on negative pitch => find best point to continue if (((diff < 0) && (newDiff >= 0) && (p > 0)) || ((diff >= 1) && (newDiff < 1) && (p < 0))) { int sig = (p > 0 ? -1 : 1); double bestOverlayScore = 9999; int bestI = 0; // Calulate slopes for the reference sample points double slope11[P_ORDER]; double slope12[P_ORDER]; for (int j = 0; j < P_ORDER; ++j) { double jpos = double (pitchBufferSize * (1 << j)) / 1000; uint32_t jptr = rPtrInt + pitchBufferSize + sig * jpos; slope11[j] = audioBuffer1[shape].frames[(jptr + 1) % pitchBufferSize] - audioBuffer1[shape].frames[jptr % pitchBufferSize]; slope12[j] = audioBuffer2[shape].frames[(jptr + 1) % pitchBufferSize] - audioBuffer2[shape].frames[jptr % pitchBufferSize]; } // Iterate through the buffer to find the best match for (int i = pitchFaderSize + 1; i < pitchBufferSize - pitchFaderSize; ++i) { double posDiff1 = audioBuffer1[shape].frames[rPtrInt % pitchBufferSize] - audioBuffer1[shape].frames[(rPtrInt + i) % pitchBufferSize]; double posDiff2 = audioBuffer2[shape].frames[rPtrInt % pitchBufferSize] - audioBuffer2[shape].frames[(rPtrInt + i) % pitchBufferSize]; double overlayScore = SQR (posDiff1) + SQR (posDiff2); for (int j = 0; j < P_ORDER; ++j) { if (overlayScore > bestOverlayScore) break; double jpos = double (pitchBufferSize * (1 << j)) / 1000; uint32_t jptr = rPtrInt + pitchBufferSize + i + sig * jpos; double slope21 = audioBuffer1[shape].frames[(jptr + 1) % pitchBufferSize] - audioBuffer1[shape].frames[jptr % pitchBufferSize]; double slope22 = audioBuffer2[shape].frames[(jptr + 1) % pitchBufferSize] - audioBuffer2[shape].frames[jptr % pitchBufferSize]; double slopeDiff1 = slope11[j] - slope21; double slopeDiff2 = slope12[j] - slope22; overlayScore += SQR (slopeDiff1) + SQR (slopeDiff2); } if (overlayScore < bestOverlayScore) { bestI = i; bestOverlayScore = overlayScore; } } newRPtr = fmod (rPtr + bestI + pitchFactor, pitchBufferSize); } audioBuffer1[shape].wPtr1 = newWPtr; audioBuffer1[shape].rPtr1 = newRPtr; audioBuffer2[shape].wPtr1 = newWPtr; audioBuffer2[shape].rPtr1 = newRPtr; } // Ring buffer method with least squares ring closure void BShapr::delay (const float input1, const float input2, float* output1, float* output2, const float delaytime, const int shape) { const int audioBufferSize = rate; const int delayBufferSize = rate * DELAYBUFFERTIME / 1000; float param = LIM (delaytime, methods[DELAY].limit.min, methods[DELAY].limit.max) * rate / 1000; const int delayframes = LIM (param, 0, audioBufferSize); const uint32_t wPtr = uint32_t (audioBuffer1[shape].wPtr1) % audioBufferSize; const uint32_t rPtr1 = uint32_t (audioBuffer1[shape].rPtr1) % audioBufferSize; const uint32_t rPtr2 = uint32_t (audioBuffer1[shape].rPtr2) % audioBufferSize; const int diff = (rPtr2 > rPtr1 ? rPtr2 - rPtr1 : rPtr2 + audioBufferSize - rPtr1); // Write to buffers and output audioBuffer1[shape].frames[wPtr] = input1; audioBuffer2[shape].frames[wPtr] = input2; *output1 = audioBuffer1[shape].frames[rPtr2]; *output2 = audioBuffer2[shape].frames[rPtr2]; // Update pointers uint32_t newRPtr1 = rPtr1; uint32_t newRPtr2 = rPtr2; // End of block? Find best point to continue. if (diff >= delayBufferSize) { double bestOverlayScore = 9999; int bestI = 0; // Calulate slopes for the reference sample points double slope11[P_ORDER]; double slope12[P_ORDER]; for (int j = 0; j < P_ORDER; ++j) { double jpos = double (delayBufferSize * (1 << j)) / 1000; uint32_t jptr = rPtr2 + audioBufferSize - jpos; slope11[j] = audioBuffer1[shape].frames[(jptr + 1) % audioBufferSize] - audioBuffer1[shape].frames[jptr % audioBufferSize]; slope12[j] = audioBuffer2[shape].frames[(jptr + 1) % audioBufferSize] - audioBuffer2[shape].frames[jptr % audioBufferSize]; } // Iterate through the buffer to find the best match for (int i = 0; (i < delayBufferSize) && (i < delayframes); ++i) { int32_t iPtr = (wPtr + 2 * audioBufferSize - delayframes - i) % audioBufferSize; double posDiff1 = audioBuffer1[shape].frames[rPtr2] - audioBuffer1[shape].frames[iPtr]; double posDiff2 = audioBuffer2[shape].frames[rPtr2] - audioBuffer2[shape].frames[iPtr]; double overlayScore = SQR (posDiff1) + SQR (posDiff2); for (int j = 0; j < P_ORDER; ++j) { if (overlayScore > bestOverlayScore) break; double jpos = double (delayBufferSize * (1 << j)) / 1000; uint32_t jptr = iPtr + audioBufferSize - jpos; double slope21 = audioBuffer1[shape].frames[(jptr + 1) % audioBufferSize] - audioBuffer1[shape].frames[jptr % audioBufferSize]; double slope22 = audioBuffer2[shape].frames[(jptr + 1) % audioBufferSize] - audioBuffer2[shape].frames[jptr % audioBufferSize]; double slopeDiff1 = slope11[j] - slope21; double slopeDiff2 = slope12[j] - slope22; overlayScore += SQR (slopeDiff1) + SQR (slopeDiff2); } if (overlayScore < bestOverlayScore) { bestI = i; bestOverlayScore = overlayScore; } } newRPtr1 = (wPtr + 2 * audioBufferSize - delayframes - bestI) % audioBufferSize; newRPtr2 = newRPtr1; } // Write back pointers audioBuffer1[shape].wPtr1 = (wPtr + 1) % audioBufferSize; audioBuffer2[shape].wPtr1 = audioBuffer1[shape].wPtr1; audioBuffer1[shape].rPtr1 = newRPtr1; audioBuffer2[shape].rPtr1 = newRPtr1; audioBuffer1[shape].rPtr2 = (newRPtr2 + 1) % audioBufferSize; audioBuffer2[shape].rPtr2 = audioBuffer1[shape].rPtr2; } // Delay with Doppler effect void BShapr::doppler (const float input1, const float input2, float* output1, float* output2, const float delaytime, const int shape) { const int audioBufferSize = rate; float param = LIM (delaytime, methods[DELAY].limit.min, methods[DELAY].limit.max) * rate / 1000; const float delayframes = LIM (param, 0, audioBufferSize); const uint32_t wPtr = uint32_t (audioBuffer1[shape].wPtr1) % audioBufferSize; const uint32_t rPtrInt = uint32_t (audioBuffer1[shape].rPtr1) % audioBufferSize; const double rPtrFrac = fmod (audioBuffer1[shape].rPtr1, 1); // Write to buffers and output audioBuffer1[shape].frames[wPtr] = input1; audioBuffer2[shape].frames[wPtr] = input2; *output1 = (1 - rPtrFrac) * audioBuffer1[shape].frames[rPtrInt] + rPtrFrac * audioBuffer1[shape].frames[(rPtrInt + 1) % audioBufferSize]; *output2 = (1 - rPtrFrac) * audioBuffer2[shape].frames[rPtrInt] + rPtrFrac * audioBuffer2[shape].frames[(rPtrInt + 1) % audioBufferSize]; // Update pointers audioBuffer1[shape].wPtr1 = (wPtr + 1) % audioBufferSize; audioBuffer2[shape].wPtr1 = audioBuffer1[shape].wPtr1; audioBuffer1[shape].rPtr1 = fmod (audioBuffer1[shape].wPtr1 + audioBufferSize - delayframes, audioBufferSize); audioBuffer2[shape].rPtr1 = audioBuffer1[shape].rPtr1; } void BShapr::distortion (const float input1, const float input2, float* output1, float* output2, const int mode, const float drive, const float limit) { const float f = db2co (LIM (drive, methods[DISTORTION].limit.min, methods[DISTORTION].limit.max)); const float l = db2co (LIM (limit, options[LIMIT_DB_OPT].limit.min, options[LIMIT_DB_OPT].limit.max)); double i1 = input1 * f / l; double i2 = input2 * f / l; switch (mode) { case HARDCLIP: *output1 = LIM (l * i1, -l, l); *output2 = LIM (l * i2, -l, l); break; case SOFTCLIP: *output1 = SGN (i1) * l * sqrt (SQR (i1) / (1 + SQR (i1))); *output2 = SGN (i2) * l * sqrt (SQR (i2) / (1 + SQR (i2))); break; case FOLDBACK: *output1 = (fabs (i1) <= 1 ? l * i1 : (SGN (i1) * l * double (2 * (int ((abs (i1) + 1) / 2) % 2) - 1) * (1.0 - fmod (fabs (i1) + 1, 2)))); *output2 = (fabs (i2) <= 1 ? l * i2 : (SGN (i2) * l * double (2 * (int ((abs (i2) + 1) / 2) % 2) - 1) * (1.0 - fmod (fabs (i2) + 1, 2)))); break; case OVERDRIVE: *output1 = ((fabs (i1) < (1.0/3.0)) ? (2.0 * l * i1) : ((fabs (i1) < (2.0/3.0)) ? (SGN (i1) * l * (3.0 - SQR (2.0 - 3.0 * fabs (i1))) / 3.0) : l * SGN (i1))); *output2 = ((fabs (i2) < (1.0/3.0)) ? (2.0 * l * i2) : ((fabs (i2) < (2.0/3.0)) ? (SGN (i2) * l * (3.0 - SQR (2.0 - 3.0 * fabs (i2))) / 3.0) : l * SGN (i2))); break; case FUZZ: *output1 = SGN (i1) * l * (1 - exp (- fabs (i1))); *output2 = SGN (i2) * l * (1 - exp (- fabs (i2))); break; default: *output1 = input1; *output2 = input2; break; } } void BShapr::decimate (const float input1, const float input2, float* output1, float* output2, const float hz, const int shape) { const double f = LIM (hz, methods[DECIMATE].limit.min, methods[DECIMATE].limit.max); if (decimateCounter[shape] + 1 >= double (rate) / f) { decimateBuffer1[shape] = input1; decimateBuffer2[shape] = input2; float c0 = double (rate) / f - decimateCounter[shape]; decimateCounter[shape] = (c0 > 0 ? c0 : 0); } else decimateCounter[shape]++; *output1 = decimateBuffer1[shape]; *output2 = decimateBuffer2[shape]; } void BShapr::bitcrush (const float input1, const float input2, float* output1, float* output2, const float bitNr) { const double f = pow (2, LIM (bitNr, methods[BITCRUSH].limit.min, methods[BITCRUSH].limit.max) - 1); const int64_t bits1 = round (double (input1) * f); const int64_t bits2 = round (double (input2) * f); *output1 = double (bits1) / f; *output2 = double (bits2) / f; } #ifdef SUPPORTS_CV void BShapr::sendCv (const float input1, const float input2, float* output1, float* output2, float* cv, const float amp) { *output1 = input1; *output2 = input2; if (cv) *cv = LIM (amp, 0.0f, 1.0f); } #else void BShapr::sendMidi (const float input1, const float input2, float* output1, float* output2, const uint8_t midiCh, const uint8_t midiCC, const float amp, uint32_t frames, const int shape) { *output1 = input1; *output2 = input2; uint8_t newValue = amp * 128; newValue = LIM (newValue, 0, 127); if (newValue != sendValue[shape]) { LV2_Atom midiatom; midiatom.type = urids.midi_Event; midiatom.size = 3; uint8_t startCh = (midiCh == 0 ? 0 : midiCh - 1); uint8_t endCh = (midiCh == 0 ? 15 : midiCh - 1); for (uint8_t ch = startCh; ch <= endCh; ++ ch) { uint8_t status = LV2_MIDI_MSG_CONTROLLER + ch; uint8_t msg[3] = {status, midiCC, newValue}; lv2_atom_forge_frame_time (&forge, frames); lv2_atom_forge_raw (&forge, &midiatom, sizeof (LV2_Atom)); lv2_atom_forge_raw (&forge, &msg, 3); lv2_atom_forge_pad (&forge, sizeof (LV2_Atom) + 3); } sendValue[shape] = newValue; } } #endif void BShapr::play (uint32_t start, uint32_t end) { if (end < start) return; #ifdef SUPPORTS_CV // Clear CV out for (int i = 0; i < MAXSHAPES; ++i) { if (cvOutputs[i]) memset(&cvOutputs[i][start], 0, (end - start) * sizeof(float)); } #endif for (uint32_t i = start; i < end; ++i) { // Interpolate position within the loop double relpos = getPositionFromFrames (i - refFrame); // Position relative to reference frame double pos = floorfrac (position + relpos); // 0..1 position float output1 = 0; float output2 = 0; // Bypass if (controllers[BYPASS] != 0.0f) { output1 = audioInput1[i]; output2 = audioInput2[i]; } // Audio calculations only if MIDI-independent or key pressed else if ((controllers[MIDI_CONTROL] == 0.0f) || (key != 0xFF)) { float input1; float input2; float shapeOutput1[MAXSHAPES]; memset (shapeOutput1, 0, MAXSHAPES * sizeof (float)); float shapeOutput2[MAXSHAPES]; memset (shapeOutput2, 0, MAXSHAPES * sizeof (float)); for (int sh = 0; sh < MAXSHAPES; ++sh) { if (controllers[SHAPERS + sh * SH_SIZE + SH_INPUT] != BShaprInputIndex::OFF) { // Connect to shaper input switch (int (controllers[SHAPERS + sh * SH_SIZE + SH_INPUT])) { case BShaprInputIndex::AUDIO_IN: input1 = audioInput1[i] * controllers[SHAPERS + sh * SH_SIZE + SH_INPUT_AMP]; input2 = audioInput2[i] * controllers[SHAPERS + sh * SH_SIZE + SH_INPUT_AMP]; break; case BShaprInputIndex::CONSTANT: input1 = controllers[SHAPERS + sh * SH_SIZE + SH_INPUT_AMP]; input2 = controllers[SHAPERS + sh * SH_SIZE + SH_INPUT_AMP]; break; default: if ((controllers[SHAPERS + sh * SH_SIZE + SH_INPUT] >= BShaprInputIndex::OUTPUT) && (controllers[SHAPERS + sh * SH_SIZE + SH_INPUT] < BShaprInputIndex::OUTPUT + MAXSHAPES)) { int inputSh = controllers[SHAPERS + sh * SH_SIZE + SH_INPUT] - BShaprInputIndex::OUTPUT; input1 = shapeOutput1[inputSh] * controllers[SHAPERS + sh * SH_SIZE + SH_INPUT_AMP]; input2 = shapeOutput2[inputSh] * controllers[SHAPERS + sh * SH_SIZE + SH_INPUT_AMP]; } else { input1 = 0; input2 = 0; } } // Get shaper value for the actual position float iFactor = 0.0f; if (((speed == 0.0f) && (controllers[BASE] != SECONDS)) || (bpm < 1.0f)) iFactor = factors[sh].getValue(); else { factors[sh].setTarget (shapes[sh].getMapValue (pos)); iFactor = factors[sh].proceed(); } float drywet = controllers[SHAPERS + sh * SH_SIZE + SH_DRY_WET]; float wet1 = 0; float wet2 = 0; // Apply shaper on target switch (int (controllers[SHAPERS + sh * SH_SIZE + SH_TARGET])) { case BShaprTargetIndex::LEVEL: audioLevel (input1, input2, &wet1, &wet2, iFactor); break; case BShaprTargetIndex::GAIN: audioLevel (input1, input2, &wet1, &wet2, db2co (LIM (iFactor, methods[GAIN].limit.min, methods[GAIN].limit.max))); break; case BShaprTargetIndex::BALANCE: stereoBalance (input1, input2, &wet1, &wet2, iFactor); break; case BShaprTargetIndex::WIDTH: stereoWidth (input1, input2, &wet1, &wet2, iFactor); break; case BShaprTargetIndex::LOW_PASS: lowPassFilter (input1, input2, &wet1, &wet2, iFactor, sh); break; case BShaprTargetIndex::LOW_PASS_LOG: lowPassFilter (input1, input2, &wet1, &wet2, pow (10, LIM (iFactor, methods[LOW_PASS_LOG].limit.min, methods[LOW_PASS_LOG].limit.max)), sh); break; case BShaprTargetIndex::HIGH_PASS: highPassFilter (input1, input2, &wet1, &wet2, iFactor, sh); break; case BShaprTargetIndex::HIGH_PASS_LOG: highPassFilter (input1, input2, &wet1, &wet2, pow (10, LIM (iFactor, methods[HIGH_PASS_LOG].limit.min, methods[HIGH_PASS_LOG].limit.max)), sh); break; case BShaprTargetIndex::PITCH: pitch (input1, input2, &wet1, &wet2, iFactor, sh); break; case BShaprTargetIndex::DELAY: delay (input1, input2, &wet1, &wet2, iFactor, sh); break; case BShaprTargetIndex::DOPPLER: doppler (input1, input2, &wet1, &wet2, iFactor, sh); break; case BShaprTargetIndex::DISTORTION: distortion ( input1, input2, &wet1, &wet2, controllers[SHAPERS + sh * SH_SIZE + SH_OPTION + DISTORTION_OPT], iFactor, controllers[SHAPERS + sh * SH_SIZE + SH_OPTION + LIMIT_DB_OPT] ); break; case BShaprTargetIndex::DECIMATE: decimate (input1, input2, &wet1, &wet2, iFactor, sh); break; case BShaprTargetIndex::BITCRUSH: bitcrush (input1, input2, &wet1, &wet2, iFactor); break; #ifdef SUPPORTS_CV case BShaprTargetIndex::SEND_CV: sendCv (input1, input2, &wet1, &wet2, (cvOutputs[sh] ? &cvOutputs[sh][i] : nullptr), iFactor); break; #else case BShaprTargetIndex::SEND_MIDI: sendMidi ( input1, input2, &wet1, &wet2, controllers[SHAPERS + sh * SH_SIZE + SH_OPTION + SEND_MIDI_CH], controllers[SHAPERS + sh * SH_SIZE + SH_OPTION + SEND_MIDI_CC], iFactor, i, sh ); break; #endif } shapeOutput1[sh] = (1 - drywet) * input1 + drywet * wet1; shapeOutput2[sh] = (1 - drywet) * input2 + drywet * wet2; if (controllers[SHAPERS + sh * SH_SIZE + SH_OUTPUT] == BShaprOutputIndex::AUDIO_OUT) { output1 += shapeOutput1[sh] * controllers[SHAPERS + sh * SH_SIZE + SH_OUTPUT_AMP]; output2 += shapeOutput2[sh] * controllers[SHAPERS + sh * SH_SIZE + SH_OUTPUT_AMP]; } } } } // Analyze input and output data for GUI notification if (ui_on) { // Calculate position in monitor int newMonitorPos = pos * MONITORBUFFERSIZE; unsigned int nr = notificationsCount % NOTIFYBUFFERSIZE; notifications[nr].position = newMonitorPos; // Position changed? => Next nr if (newMonitorPos != monitorPos) { ++notificationsCount; stepCount = 0; nr = notificationsCount % NOTIFYBUFFERSIZE; memset(¬ifications[nr], 0, sizeof (BShaprNotifications)); monitorPos = newMonitorPos; } ++stepCount; float fstep = 1 / stepCount; float fprev = (stepCount - 1) * fstep; if (audioInput1[i] < 0) notifications[nr].input1.min = fprev * notifications[nr].input1.min + fstep * audioInput1[i]; else notifications[nr].input1.max = fprev * notifications[nr].input1.max + fstep * audioInput1[i]; if (output1 < 0) notifications[nr].output1.min = fprev * notifications[nr].output1.min + fstep * output1; else notifications[nr].output1.max = fprev * notifications[nr].output1.max + fstep * output1; if (audioInput2[i] < 0) notifications[nr].input2.min = fprev * notifications[nr].input2.min + fstep * audioInput2[i]; else notifications[nr].input2.max = fprev * notifications[nr].input2.max + fstep * audioInput2[i]; if (output2 < 0) notifications[nr].output2.min = fprev * notifications[nr].output2.min + fstep * output2; else notifications[nr].output2.max = fprev * notifications[nr].output2.max + fstep * output2; } // Store in audio out audioOutput1[i] = audioInput1[i] * (1 - controllers[DRY_WET]) + output1 * controllers[DRY_WET]; audioOutput2[i] = audioInput2[i] * (1 - controllers[DRY_WET]) + output2 * controllers[DRY_WET]; } } LV2_State_Status BShapr::state_save (LV2_State_Store_Function store, LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features) { char shapesDataString[0x8010] = "Shape data:\n"; for (unsigned int sh = 0; sh < MAXSHAPES; ++sh) { for (unsigned int nd = 0; nd < shapes[sh].size (); ++nd) { char valueString[160]; Node node = shapes[sh].getNode (nd); snprintf ( valueString, 126, "shp:%d; met:%d; typ:%d; ptx:%f; pty:%f; h1x:%f; h1y:%f; h2x:%f; h2y:%f", sh, int (controllers[SHAPERS + sh * SH_SIZE + SH_TARGET]), int (node.nodeType), node.point.x, node.point.y, node.handle1.x, node.handle1.y, node.handle2.x, node.handle2.y ); if ((sh < MAXSHAPES - 1) || nd < shapes[sh].size ()) strcat (valueString, ";\n"); else strcat(valueString, "\n"); strcat (shapesDataString, valueString); } } store (handle, urids.state_shape, shapesDataString, strlen (shapesDataString) + 1, urids.atom_String, LV2_STATE_IS_POD); return LV2_STATE_SUCCESS; } LV2_State_Status BShapr::state_restore (LV2_State_Retrieve_Function retrieve, LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features) { size_t size; uint32_t type; uint32_t valflags; const void* shapesData = retrieve(handle, urids.state_shape, &size, &type, &valflags); if (shapesData && (type == urids.atom_String)) { // Clear old data for (int i = 0; i < MAXSHAPES; ++i) shapes[i].clearShape (); // Parse retrieved data std::string shapesDataString = (char*) shapesData; const std::string keywords[9] = {"shp:", "met:", "typ:", "ptx:", "pty:", "h1x:", "h1y:", "h2x:", "h2y:"}; while (!shapesDataString.empty()) { // Look for next "shp:" size_t strPos = shapesDataString.find ("shp:"); size_t nextPos = 0; if (strPos == std::string::npos) break; // No "shp:" found => end if (strPos + 4 > shapesDataString.length()) break; // Nothing more after id => end shapesDataString.erase (0, strPos + 4); int sh; try {sh = BUtilities::stof (shapesDataString, &nextPos);} catch (const std::exception& e) { fprintf (stderr, "BShapr.lv2: Restore shape state incomplete. Can't parse shape number from \"%s...\"", shapesDataString.substr (0, 63).c_str()); break; } if (nextPos > 0) shapesDataString.erase (0, nextPos); if ((sh < 0) || (sh >= MAXSHAPES)) { fprintf (stderr, "BShapr.lv2: Restore shape state incomplete. Invalid matrix data block loaded for shape %i.\n", sh); break; } // Look for shape data Node node = {NodeType::POINT_NODE, {0, 0}, {0, 0}, {0, 0}}; bool isTypeDef = false; int methodNr = -1; for (int i = 1; i < 9; ++i) { strPos = shapesDataString.find (keywords[i]); if (strPos == std::string::npos) continue; // Keyword not found => next keyword if (strPos + 4 >= shapesDataString.length()) // Nothing more after keyword => end { shapesDataString =""; break; } if (strPos > 0) shapesDataString.erase (0, strPos + 4); float val; try {val = BUtilities::stof (shapesDataString, &nextPos);} catch (const std::exception& e) { fprintf (stderr, "BShapr.lv2: Restore shape state incomplete. Can't parse %s from \"%s...\"", keywords[i].substr(0,3).c_str(), shapesDataString.substr (0, 63).c_str()); break; } if (nextPos > 0) shapesDataString.erase (0, nextPos); switch (i) { case 1: methodNr = LIM (val, 0, MAXEFFECTS - 1); break; case 2: node.nodeType = (NodeType)((int)val); isTypeDef = true; break; case 3: node.point.x = val; break; case 4: node.point.y = val; break; case 5: node.handle1.x = val; break; case 6: node.handle1.y = val; break; case 7: node.handle2.x = val; break; case 8: node.handle2.y = val; break; default:break; } } // Set data if (isTypeDef) { if (methodNr >=0) { shapes[sh].setTransformation (methods[methodNr].transformFactor, methods[methodNr].transformOffset); shapes[sh].appendNode (node); } // Old versions (< 0.7): temp. store node until method is set else { tempNodes[sh].push_back (node); } } } // Validate all shapes for (int i = 0; i < MAXSHAPES; ++i) { if (shapes[i].size () < 2) shapes[i].setDefaultShape (); else if (!shapes[i].validateShape ()) shapes[i].setDefaultShape (); } // Force GUI notification for (int i = 0; i < MAXSHAPES; ++i) scheduleNotifyShapes[i] = true; } return LV2_STATE_SUCCESS; } static LV2_Handle instantiate (const LV2_Descriptor* descriptor, double samplerate, const char* bundle_path, const LV2_Feature* const* features) { // New instance BShapr* instance; try {instance = new BShapr(samplerate, features);} catch (std::exception& exc) { fprintf (stderr, "BShapr.lv2: Plugin instantiation failed. %s\n", exc.what ()); return NULL; } if (!instance) { fprintf(stderr, "BShapr.lv2: Plugin instantiation failed.\n"); return NULL; } if (!instance->map) { fprintf(stderr, "BShapr.lv2: Host does not support urid:map.\n"); delete (instance); return NULL; } return (LV2_Handle)instance; } static void connect_port (LV2_Handle instance, uint32_t port, void *data) { BShapr* inst = (BShapr*) instance; if (inst) inst->connect_port (port, data); } static void run (LV2_Handle instance, uint32_t n_samples) { BShapr* inst = (BShapr*) instance; if (inst) inst->run (n_samples); } static void cleanup (LV2_Handle instance) { BShapr* inst = (BShapr*) instance; if (inst) delete inst; } static LV2_State_Status state_save(LV2_Handle instance, LV2_State_Store_Function store, LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features) { BShapr* inst = (BShapr*)instance; if (!inst) return LV2_STATE_SUCCESS; inst->state_save (store, handle, flags, features); return LV2_STATE_SUCCESS; } static LV2_State_Status state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features) { BShapr* inst = (BShapr*)instance; if (inst) inst->state_restore (retrieve, handle, flags, features); return LV2_STATE_SUCCESS; } static const void* extension_data(const char* uri) { static const LV2_State_Interface state = {state_save, state_restore}; if (!strcmp(uri, LV2_STATE__interface)) { return &state; } return NULL; } static const LV2_Descriptor descriptor = { BSHAPR_URI, instantiate, connect_port, NULL, //activate, run, NULL, //deactivate, cleanup, extension_data }; // LV2 Symbol Export LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index) { switch (index) { case 0: return &descriptor; default: return NULL; } } BShapr-0.13/src/BShapr.hpp000066400000000000000000000145121405711530000152720ustar00rootroot00000000000000/* B.Shapr * Beat / envelope shaper LV2 plugin * * Copyright (C) 2019 by Sven Jähnichen * * This program 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, or (at your option) * any later version. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef BSHAPR_HPP_ #define BSHAPR_HPP_ #include #include #include #include #include #include #include #include #include #include #include "Globals.hpp" #include "Urids.hpp" #include "BUtilities/Point.hpp" #include "Node.hpp" #include "Shape.hpp" #include "BShaprNotifications.hpp" #define MAX_F_ORDER 12 #define P_ORDER 6 #define PITCHBUFFERTIME 20 #define PITCHFADERTIME 2 #define DELAYBUFFERTIME 20 #define MINOPTIONVALUE -20000 #define MAXOPTIONVALUE 20000 struct AudioBuffer { AudioBuffer (); AudioBuffer (const uint32_t size); ~AudioBuffer (); float* frames; double wPtr1, wPtr2, rPtr1, rPtr2; uint32_t size; void resize (const uint32_t size); void reset (); }; class Message { public: Message (); void clearMessages (); void setMessage (MessageNr messageNr); void deleteMessage (MessageNr messageNr); bool isMessage (MessageNr messageNr); MessageNr loadMessage (); bool isScheduled (); private: uint32_t messageBits; bool scheduled; }; class Fader { public: Fader (); Fader (const float value, const float speed); void setTarget (const float target); void setSpeed (const float speed); float proceed (); float getValue () const; protected: float value; float target; float speed; }; class BShapr { public: BShapr (double samplerate, const LV2_Feature* const* features); ~BShapr(); void connect_port (uint32_t port, void *data); void run (uint32_t n_samples); LV2_State_Status state_save(LV2_State_Store_Function store, LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features); LV2_State_Status state_restore(LV2_State_Retrieve_Function retrieve, LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features); LV2_URID_Map* map; private: void fillFilterBuffer (float filterBuffer[MAXSHAPES] [MAX_F_ORDER / 2], const float value); bool isAudioOutputConnected (int shapeNr); void audioLevel (const float input1, const float input2, float* output1, float* output2, const float amp); void stereoBalance (const float input1, const float input2, float* output1, float* output2, const float balance); void stereoWidth (const float input1, const float input2, float* output1, float* output2, const float width); void lowPassFilter (const float input1, const float input2, float* output1, float* output2, const float cutoffFreq, const int shape); void highPassFilter (const float input1, const float input2, float* output1, float* output2, const float cutoffFreq, const int shape); void pitch (const float input1, const float input2, float* output1, float* output2, const float semitone, const int shape); void delay (const float input1, const float input2, float* output1, float* output2, const float delaytime, const int shape); void doppler (const float input1, const float input2, float* output1, float* output2, const float delaytime, const int shape); void decimate (const float input1, const float input2, float* output1, float* output2, const float hz, const int shape); void distortion (const float input1, const float input2, float* output1, float* output2, const int mode, const float drive, const float limit); void bitcrush (const float input1, const float input2, float* output1, float* output2, const float bitNr); #ifdef SUPPORTS_CV void sendCv (const float input1, const float input2, float* output1, float* output2, float* cv, const float amp); #else void sendMidi (const float input1, const float input2, float* output1, float* output2, const uint8_t midiCh, const uint8_t midiCC, const float amp, const uint32_t frames, const int shape); #endif void play(uint32_t start, uint32_t end); void notifyMonitorToGui (); void notifyShapeToGui (int shapeNr); void notifyMessageToGui (); void notifyStatusToGui (); double getPositionFromBeats (double beats); double getPositionFromFrames (uint64_t frames); double getPositionFromSeconds (double seconds); double rate; float bpm; float speed; uint64_t bar; float barBeat; float beatsPerBar; uint32_t beatUnit; double position; double offset; uint64_t refFrame; // Audio buffers float* audioInput1; float* audioInput2; float* audioOutput1; float* audioOutput2; AudioBuffer audioBuffer1 [MAXSHAPES]; AudioBuffer audioBuffer2 [MAXSHAPES]; float filter1Buffer1 [MAXSHAPES] [MAX_F_ORDER / 2]; float filter1Buffer2 [MAXSHAPES] [MAX_F_ORDER / 2]; float filter2Buffer1 [MAXSHAPES] [MAX_F_ORDER / 2]; float filter2Buffer2 [MAXSHAPES] [MAX_F_ORDER / 2]; float decimateBuffer1 [MAXSHAPES]; float decimateBuffer2 [MAXSHAPES]; double decimateCounter [MAXSHAPES]; uint8_t sendValue [MAXSHAPES]; Fader factors[MAXSHAPES]; // Controllers float* new_controllers[NR_CONTROLLERS]; float controllers [NR_CONTROLLERS]; // Nodes and Maps Shape shapes[MAXSHAPES]; StaticArrayList tempNodes[MAXSHAPES]; // Atom port BShaprURIDs urids; LV2_Atom_Sequence* controlPort; LV2_Atom_Sequence* notifyPort; #ifdef SUPPORTS_CV float* cvOutputs[MAXSHAPES]; #endif LV2_Atom_Forge forge; LV2_Atom_Forge_Frame notify_frame; // Data buffers float nodeBuffer[7]; float shapeBuffer[MAXNODES * 7]; // MIDI uint8_t key; // Internals bool ui_on; Message message; int monitorPos; unsigned int notificationsCount; float stepCount; std::array notifications; bool scheduleNotifyShapes[MAXSHAPES]; bool scheduleNotifyStatus; }; #endif /* BSHAPR_HPP_ */ BShapr-0.13/src/BShaprGUI.cpp000066400000000000000000001623741405711530000156440ustar00rootroot00000000000000/* B.Shapr * Beat / envelope shaper LV2 plugin * * Copyright (C) 2019 by Sven Jähnichen * * This program 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, or (at your option) * any later version. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "BShaprGUI.hpp" BShaprGUI::BShaprGUI (const char *bundlePath, const LV2_Feature *const *features, PuglNativeView parentWindow) : Window (1200, 710, "B.Shapr", parentWindow, true, PUGL_MODULE, 0), controller (NULL), write_function (NULL), bpm (120), beatsPerBar (4.0), beatUnit (4), mContainer (0, 0, 1200, 710, "widget"), messageLabel (500, 45, 500, 20, "label", ""), bypassButton (1090, 15, 16, 16, "redbutton"), drywetDial (1144, 5, 33, 40, "dial", 1.0, 0.0, 1.0, 0.0, "%1.2f"), midiTriggerSwitch (760, 568, 30, 12, "dial", 0), midiPiano (760, 585, 150, 30, "widget", 0, 11), midiThruSwitch (1044, 575, 12, 30, "dial", 0), baseValueSelect (480, 660, 100, 20, "select", 1.0, 1.0, 16.0, 0.01), baseListBox (620, 660, 100, 20, 0, -80, 100, 80, "menu", BItems::ItemList ({{0, "Seconds"}, {1, "Beats"}, {2, "Bars"}})), monitorContainer (24, 134, 1152, 352, "monitor"), monitorHorizon1 (0, 0, 0, 64, 352, "horizon"), monitorHorizon2 (-1152, 0, 0, 64, 352, "horizon"), input1Monitor (0, 0, 1152, 176, "monitor.in"), output1Monitor (0, 0, 1152, 176, "monitor.out"), input2Monitor (0, 176, 1152, 176, "monitor.in"), output2Monitor (0, 176, 1152, 176, "monitor.out"), shapeBuffer {0.0}, horizonPos (0), monitorScale (0.25), minorXSteps (1.0), majorXSteps (1.0), clipboard (), pluginPath (bundlePath ? std::string (bundlePath) : std::string ("")), sz (1.0), bgImageSurface (nullptr), forge (), urids (), map (NULL) { // Init shapes for (int i = 0; i < MAXSHAPES; ++i) { shapeGui[i].tabContainer = BWidgets::Widget (20 + i * 148, 90, 147, 40, "tab"); shapeGui[i].tabIcon = BWidgets::ImageIcon (0, 12.5, 120, 15, "widget", pluginPath + "inc/Shape" + std::to_string (i + 1) + ".png"); for (int j = 0; j< NRSYMBOLS; ++j) shapeGui[i].tabSymbol[j] = SymbolWidget (120 + (j % 2) * 14 , 8 + int (j / 2) * 14, 10, 10, "symbol", SWSymbol(j)); shapeGui[i].shapeContainer = BWidgets::Widget (20, 130, 1160, 510, "widget"); // Method menu BItems::ItemList il; shapeGui[i].methodIcons = {}; for (int j = 0; j < MAXEFFECTS; ++j) { std::string iconPath = ""; int index = 0; for (int k = 0; k < MAXEFFECTS; ++k) { if (j == methods[k].listIndex) { iconPath = pluginPath + methods[k].iconFileName; index = k; break; } } shapeGui[i].methodIcons.push_back (BWidgets::ImageIcon (0, 0, 154, 54, "icon", std::vector{iconPath, iconPath})); BWidgets::ImageIcon* icon = &*std::prev (shapeGui[i].methodIcons.end ()); cairo_t* cr = cairo_create (icon->getIconSurface (BColors::NORMAL)); cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5); cairo_paint (cr); cairo_destroy (cr); il.push_back (BItems::Item (index, icon)); } shapeGui[i].targetListBox = BWidgets::PopupListBox (20, 443, 174, 54, 0, -380, 154, 380, "menu2", il, 0); shapeGui[i].shapeWidget = ShapeWidget (4, 4, 1152, 352, "shape"); shapeGui[i].tabMsgBox = nullptr; shapeGui[i].tabMsgBoxBg = nullptr; shapeGui[i].smoothingLabel = BWidgets::Label (960, 410, 40, 10, "ssmlabel", "Smooth"); shapeGui[i].smoothingDial = BWidgets::DialValue (960, 366, 40, 48, "dial", 20.0, 0.0, 100.0, 0, "%3.1f ms"); shapeGui[i].toolSelect = SelectWidget (133, 368, 284, 44, "tool", 44, 44, 5, 2, {"Select", "Point node", "Auto Bezier node", "Symmetric Bezier node", "Asymmetric Bezier node"}); for (int j = 0; j < 4; ++j) shapeGui[i].editWidgets[j] = EditWidget (463 + j * 60, 368, 44, 44, "widget", editWidgetLabels[j]); for (int j = 4; j < 7; ++j) shapeGui[i].editWidgets[j] = EditWidget (503 + j * 60, 368, 44, 44, "widget", editWidgetLabels[j]); shapeGui[i].gridSelect = SelectWidget (1043, 368, 104, 44, "tool", 44, 44, 2, 2, {"Show grid", "Snap to grid"}); shapeGui[i].drywetLabel = BWidgets::Label (500, 494, 50, 16, "smlabel", "dry/wet"); shapeGui[i].drywetDial = BWidgets::DialValue (500, 434, 50, 60, "dial", 1.0, 0.0, 1.0, 0, "%1.2f"); for (int j = 0; j < MAXOPTIONS; ++j) { switch (options[j].widgetType) { case NO_WIDGET: shapeGui[i].optionWidgets[j] = nullptr; break; case DIAL_WIDGET: shapeGui[i].optionWidgets[j] = new BWidgets::DialValue ( 0, 0, 50, 60, "dial", options[j].value, options[j].limit.min, options[j].limit.max, options[j].limit.step, options[j].param.get() ); if (!shapeGui[i].optionWidgets[j]) throw std::bad_alloc(); shapeGui[i].optionWidgets[j]->setHardChangeable (false); shapeGui[i].optionLabels[j] = BWidgets::Label (220 + j * 70, 494, 70, 16, "smlabel", options[j].name); break; case POPUP_WIDGET: { BItems::ItemList il = options[j].param.get(); size_t max = 0; for (BItems::Item const& it : il) { if (it.getWidget()) { BWidgets::Label* l = (BWidgets::Label*) it.getWidget(); if (l->getText().size() > max) max = l->getText().size(); } } int w = max * 9 + 20; w = LIMIT (w, 50, 270 - j * 70); int h = (il.size() + 1) * 20; h = LIMIT (h, 20, 400); shapeGui[i].optionWidgets[j] = new BWidgets::PopupListBox ( 0, 0, w, 20, 0, -h, w, h, "menu", il, options[j].value ); if (!shapeGui[i].optionWidgets[j]) throw std::bad_alloc(); shapeGui[i].optionLabels[j] = BWidgets::Label (220 + j * 70, 494, w, 16, "smlabel", options[j].name); } break; default: shapeGui[i].optionWidgets[j] = nullptr; break; } } } // Init main monitor initMonitors (); // Link controller widgets controllerWidgets.fill (nullptr); controllerWidgets[BYPASS] = (BWidgets::ValueWidget*) &bypassButton; controllerWidgets[DRY_WET] = (BWidgets::ValueWidget*) &drywetDial; controllerWidgets[MIDI_CONTROL] = (BWidgets::ValueWidget*) &midiTriggerSwitch; controllerWidgets[MIDI_THRU] = (BWidgets::ValueWidget*) &midiThruSwitch; controllerWidgets[BASE] = (BWidgets::ValueWidget*) &baseListBox; controllerWidgets[BASE_VALUE] = (BWidgets::ValueWidget*) &baseValueSelect; for (int i = 0; i < MAXSHAPES; ++i) { controllerWidgets[SHAPERS + i * SH_SIZE + SH_TARGET] = (BWidgets::ValueWidget*) &shapeGui[i].targetListBox; controllerWidgets[SHAPERS + i * SH_SIZE + SH_SMOOTHING] = (BWidgets::ValueWidget*) &shapeGui[i].smoothingDial; controllerWidgets[SHAPERS + i * SH_SIZE + SH_DRY_WET] = (BWidgets::ValueWidget*) &shapeGui[i].drywetDial; for (int j = 0 ; j < MAXOPTIONS; ++j) { controllerWidgets[SHAPERS + i * SH_SIZE + SH_OPTION + j] = (shapeGui[i].optionWidgets[j] ? shapeGui[i].optionWidgets[j] : nullptr); } } // Init controllers for (int i = 0; i < NR_CONTROLLERS; ++i) controllers[i] = (controllerWidgets[i] ? controllerWidgets[i]->getValue () : 0); // Set callbacks bypassButton.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); drywetDial.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); midiTriggerSwitch.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); midiPiano.setCallbackFunction (BEvents::EventType::BUTTON_PRESS_EVENT, BShaprGUI::pianoCallback); midiPiano.setCallbackFunction (BEvents::EventType::BUTTON_RELEASE_EVENT, BShaprGUI::pianoCallback); midiPiano.setCallbackFunction (BEvents::EventType::POINTER_DRAG_EVENT, BShaprGUI::pianoCallback); midiThruSwitch.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); baseValueSelect.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); baseListBox.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); monitorContainer.setCallbackFunction (BEvents::EventType::WHEEL_SCROLL_EVENT, BShaprGUI::wheelScrolledCallback); for (int i = 0; i < MAXSHAPES; ++i) { shapeGui[i].tabContainer.setCallbackFunction (BEvents::EventType::BUTTON_PRESS_EVENT, BShaprGUI::tabClickedCallback); shapeGui[i].tabIcon.setCallbackFunction (BEvents::EventType::BUTTON_PRESS_EVENT, BShaprGUI::tabClickedCallback); for (int j = 0; j< NRSYMBOLS; ++j) shapeGui[i].tabSymbol[j].setCallbackFunction (BEvents::EventType::BUTTON_PRESS_EVENT, BShaprGUI::tabClickedCallback); shapeGui[i].smoothingDial.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); shapeGui[i].targetListBox.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); shapeGui[i].drywetDial.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); shapeGui[i].shapeWidget.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::shapeChangedCallback); shapeGui[i].toolSelect.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::toolChangedCallback); for (int j = 0; j < 7; ++j) shapeGui[i].editWidgets[j].setCallbackFunction (BEvents::EventType::BUTTON_PRESS_EVENT, BShaprGUI::editClickedCallback); for (int j = 0; j < 7; ++j) shapeGui[i].editWidgets[j].setCallbackFunction (BEvents::EventType::BUTTON_RELEASE_EVENT, BShaprGUI::editReleasedCallback); shapeGui[i].gridSelect.setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::gridChangedCallback); for (int j = 0 ; j < MAXOPTIONS; ++j) { if (shapeGui[i].optionWidgets[j]) shapeGui[i].optionWidgets[j]->setCallbackFunction (BEvents::EventType::VALUE_CHANGED_EVENT, BShaprGUI::valueChangedCallback); } shapeGui[i].shapeWidget.setMergeable (BEvents::POINTER_DRAG_EVENT, false); } // Configure widgets calculateXSteps (); mContainer.loadImage (BColors::NORMAL, pluginPath + BG_FILE); std::vector keys (12, true); drywetDial.setHardChangeable (false); midiPiano.setKeysToggleable (true); midiPiano.pressKeys (keys); midiPiano.hide(); monitorContainer.setScrollable (true); input1Monitor.setScrollable (false); output1Monitor.setScrollable (false); input2Monitor.setScrollable (false); output2Monitor.setScrollable (false); shapeGui[0].tabContainer.rename ("activetab"); for (unsigned int i = 0; i < MAXSHAPES; ++i) { for (int j = 0; j< NRSYMBOLS; ++j) { if (j != CLOSESYMBOL) shapeGui[i].tabSymbol[j].hide (); } shapeGui[i].shapeWidget.setTool (ToolType::POINT_NODE_TOOL); shapeGui[i].shapeWidget.setScaleParameters (methods[0].anchorYPos, methods[0].anchorValue, methods[0].ratio); shapeGui[i].shapeWidget.setLowerLimit (methods[0].limit.min); shapeGui[i].shapeWidget.setHigherLimit (methods[0].limit.max); shapeGui[i].shapeWidget.setTransformation (methods[0].transformFactor, methods[0].transformOffset); shapeGui[i].smoothingDial.setHardChangeable (false); shapeGui[i].drywetDial.setHardChangeable (false); for (int j = 0; j < MAXOPTIONS; ++j) { if (shapeGui[i].optionWidgets[j]) shapeGui[i].optionWidgets[j]->hide (); shapeGui[i].optionLabels[j].hide (); } shapeGui[i].shapeContainer.setScrollable (false); if (i >= 1) shapeGui[i].shapeContainer.hide (); } applyChildThemes (); getKeyGrabStack()->add (this); // Pack widgets monitorContainer.add (input1Monitor); monitorContainer.add (output1Monitor); monitorContainer.add (input2Monitor); monitorContainer.add (output2Monitor); monitorContainer.add (monitorHorizon1); monitorContainer.add (monitorHorizon2); mContainer.add (monitorContainer); for (unsigned int i = 0; i < MAXSHAPES; ++i) { shapeGui[i].tabContainer.add (shapeGui[i].tabIcon); for (int j = 0; j< NRSYMBOLS; ++j) shapeGui[i].tabContainer.add (shapeGui[i].tabSymbol[j]); mContainer.add (shapeGui[i].tabContainer); shapeGui[i].shapeContainer.add (shapeGui[i].smoothingLabel); shapeGui[i].shapeContainer.add (shapeGui[i].smoothingDial); shapeGui[i].shapeContainer.add (shapeGui[i].drywetLabel); shapeGui[i].shapeContainer.add (shapeGui[i].drywetDial); shapeGui[i].shapeContainer.add (shapeGui[i].shapeWidget); shapeGui[i].shapeContainer.add (shapeGui[i].toolSelect); for (int j = 0; j < 7; ++j) shapeGui[i].shapeContainer.add (shapeGui[i].editWidgets[j]); shapeGui[i].shapeContainer.add (shapeGui[i].gridSelect); shapeGui[i].shapeContainer.add (shapeGui[i].targetListBox); for (int j = 0; j < MAXOPTIONS; ++j) { if (shapeGui[i].optionWidgets[j]) shapeGui[i].shapeContainer.add (*shapeGui[i].optionWidgets[j]); shapeGui[i].shapeContainer.add (shapeGui[i].optionLabels[j]); } mContainer.add (shapeGui[i].shapeContainer); } mContainer.add (bypassButton); mContainer.add (drywetDial); mContainer.add (midiTriggerSwitch); mContainer.add (midiPiano); mContainer.add (midiThruSwitch); mContainer.add (messageLabel); mContainer.add (baseValueSelect); mContainer.add (baseListBox); add (mContainer); // Post addition configurations for (int i = 0; i < MAXSHAPES; ++i) { shapeGui[i].shapeWidget.setDefaultShape(); shapeGui[i].shapeWidget.setValueEnabled (true); } //Scan host features for URID map LV2_URID_Map* m = NULL; for (int i = 0; features[i]; ++i) { if (strcmp(features[i]->URI, LV2_URID__map) == 0) m = (LV2_URID_Map*) features[i]->data; } if (!m) throw std::invalid_argument ("Host does not support urid:map"); //Map URIS map = m; mapURIDs (map, &urids); // Initialize forge lv2_atom_forge_init (&forge, map); } BShaprGUI::~BShaprGUI() { for (int i = 0; i < MAXSHAPES; ++i) { for (int j = 0; j < MAXOPTIONS; ++j) { if (shapeGui[i].optionWidgets[j]) delete shapeGui[i].optionWidgets[j]; } if (shapeGui[i].tabMsgBox) delete shapeGui[i].tabMsgBox; if (shapeGui[i].tabMsgBoxBg) delete shapeGui[i].tabMsgBoxBg; } sendGuiOff (); } void BShaprGUI::portEvent(uint32_t port, uint32_t bufferSize, uint32_t format, const void* buffer) { // Notify port if ((format == urids.atom_eventTransfer) && (port == NOTIFY)) { const LV2_Atom* atom = (const LV2_Atom*) buffer; if ((atom->type == urids.atom_Blank) || (atom->type == urids.atom_Object)) { const LV2_Atom_Object* obj = (const LV2_Atom_Object*) atom; // Monitor notification if (obj->body.otype == urids.notify_monitorEvent) { const LV2_Atom* data = NULL; lv2_atom_object_get(obj, urids.notify_monitor, &data, 0); if (data && (data->type == urids.atom_Vector)) { const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*) data; if (vec->body.child_type == urids.atom_Float) { uint32_t notificationsCount = (uint32_t) ((data->size - sizeof(LV2_Atom_Vector_Body)) / sizeof (BShaprNotifications)); BShaprNotifications* notifications = (BShaprNotifications*) (&vec->body + 1); if (notificationsCount > 0) { std::pair pos = translateNotification (notifications, notificationsCount); int p1 = LIMIT (pos.first, 0, MONITORBUFFERSIZE - 1); int p2 = LIMIT (pos.second, 0, MONITORBUFFERSIZE - 1); if (p1 <= p2) { updateMonitors (p1, p2); updateHorizon (); } else { updateMonitors (p1, MONITORBUFFERSIZE - 1); updateMonitors (0, p2); updateHorizon (); } } } } else std::cerr << "BShapr.lv2#GUI: Corrupt audio message." << std::endl; } // Message notification else if (obj->body.otype == urids.notify_messageEvent) { const LV2_Atom* data = NULL; lv2_atom_object_get(obj, urids.notify_message, &data, 0); if (data && (data->type == urids.atom_Int)) { const int messageNr = ((LV2_Atom_Int*)data)->body; std::string msg = ((messageNr >= NO_MSG) && (messageNr <= MAX_MSG) ? messageStrings[messageNr] : ""); messageLabel.setText (msg); } } // Status notification else if (obj->body.otype == urids.notify_statusEvent) { const LV2_Atom *oBpb = NULL, *oBu = NULL, *oBpm = NULL; lv2_atom_object_get ( obj, urids.time_beatsPerBar, &oBpb, urids.time_beatUnit, &oBu, urids.time_beatsPerMinute, &oBpm, 0); if (oBpb && (oBpb->type == urids.atom_Float)) { float value = ((LV2_Atom_Float*)oBpb)->body; if (value != 0.0f) { beatsPerBar = value; calculateXSteps (); updateHorizon (); } } if (oBu && (oBu->type == urids.atom_Int)) { float value = ((LV2_Atom_Float*)oBu)->body; if (int (value) != 0) { beatUnit = value; calculateXSteps (); } } if (oBpm && (oBpm->type == urids.atom_Float)) { float value = ((LV2_Atom_Float*)oBpm)->body; if (value != 0.0f) { bpm = value; updateHorizon (); } } } // Shape notification else if (obj->body.otype == urids.notify_shapeEvent) { LV2_Atom *sNr = NULL, *sData = NULL; lv2_atom_object_get (obj, urids.notify_shapeNr, &sNr, urids.notify_shapeData, &sData, 0); if (sNr && (sNr->type == urids.atom_Int) && sData && (sData->type == urids.atom_Vector)) { int shapeNr = ((LV2_Atom_Int*)sNr)->body; if ((shapeNr >= 0) && (shapeNr < MAXSHAPES)) { const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*) sData; size_t vecSize = (sData->size - sizeof(LV2_Atom_Vector_Body)) / (7 * sizeof (float)); if (vec->body.child_type == urids.atom_Float) { shapeGui[shapeNr].shapeWidget.setValueEnabled (false); shapeGui[shapeNr].shapeWidget.clearShape (); float* data = (float*)(&vec->body + 1); for (unsigned int nodeNr = 0; (nodeNr < vecSize) && (nodeNr < MAXNODES); ++nodeNr) { Node node (&data[nodeNr * 7]); shapeGui[shapeNr].shapeWidget.appendRawNode (node); } shapeGui[shapeNr].shapeWidget.validateShape(); shapeGui[shapeNr].shapeWidget.pushToSnapshots (); shapeGui[shapeNr].shapeWidget.update (); shapeGui[shapeNr].shapeWidget.setValueEnabled (true); } } } } } } // Scan controller ports else if ((format == 0) && (port >= CONTROLLERS) && (port < CONTROLLERS + NR_CONTROLLERS)) { // Calulate controller nr and validate controller value int controllerNr = port - CONTROLLERS; float* pval = (float*) buffer; if (controllerNr < SHAPERS) { float oldValue = controllers[controllerNr]; float value = globalControllerLimits[controllerNr].validate (*pval); controllers[controllerNr] = value; // Active shape if (controllerNr == ACTIVE_SHAPE) { int sh = LIMIT (oldValue, 1, MAXSHAPES) - 1; shapeGui[sh].tabContainer.rename ("tab"); shapeGui[sh].tabContainer.applyTheme (theme); shapeGui[sh].shapeContainer.hide(); int nsh = value - 1; shapeGui[nsh].tabContainer.rename ("activetab"); shapeGui[nsh].tabContainer.applyTheme (theme); shapeGui[nsh].shapeContainer.show(); updateHorizon (); } // Keys else if (controllerNr == MIDI_KEYS) { uint32_t bits = value; std::vector keys (12, false); for (int i = 0; i < 12; ++i) { if (bits & (1 << i)) keys[i] = true; } midiPiano.pressKeys (keys); } // MIDI connected ? // Show or hide piano else if (controllerNr == MIDI_CONTROL) { if (controllerWidgets[MIDI_CONTROL]) controllerWidgets[MIDI_CONTROL]->setValue (value); if (value == 1.0f) midiPiano.show (); else midiPiano.hide (); } // Base, base value // Update recalculate shaper grid steps else if ((controllerNr == BASE) || (controllerNr == BASE_VALUE)) { if (controllerWidgets[controllerNr]) controllerWidgets[controllerNr]->setValue (value); } } // Shapers else if ((controllerNr >= SHAPERS) && (controllerNr < SHAPERS + SH_SIZE * MAXSHAPES)) { int shapeNr = (port - CONTROLLERS - SHAPERS) / SH_SIZE; int shapeWidgetNr = port - CONTROLLERS - SHAPERS - shapeNr * SH_SIZE; float value = shapeControllerLimits[shapeWidgetNr].validate (*pval); controllers[controllerNr] = value; if (controllerWidgets[controllerNr]) controllerWidgets[controllerNr]->setValue (value); // SH_OUTPUT if (shapeWidgetNr == SH_OUTPUT) updateTabs (); } } } void BShaprGUI::updateTabs () { int lastShape = 0; for (int i = 0; i < MAXSHAPES; ++i) { if (controllers[SHAPERS + i * SH_SIZE + SH_OUTPUT] == AUDIO_OUT) lastShape = i; } // Show or hide tabs for (int i = 0; i < MAXSHAPES; ++i) { if (i <= lastShape) shapeGui[i].tabContainer.show (); else shapeGui[i].tabContainer.hide (); } // Show or hide tabSymbols for (int i = 0; i < MAXSHAPES; ++i) { if (lastShape + 1 < MAXSHAPES) shapeGui[i].tabSymbol[ADDSYMBOL].show (); else shapeGui[i].tabSymbol[ADDSYMBOL].hide (); if ((i > 0) && (i <= lastShape)) shapeGui[i].tabSymbol[LEFTSYMBOL].show (); else shapeGui[i].tabSymbol[LEFTSYMBOL].hide (); if (i < lastShape) shapeGui[i].tabSymbol[RIGHTSYMBOL].show (); else shapeGui[i].tabSymbol[RIGHTSYMBOL].hide (); } } void BShaprGUI::resizeGUI(const double sz) { hide (); // Resize Fonts defaultFont.setFontSize (12.0 * sz); smFont.setFontSize (10.0 * sz); ssmFont.setFontSize (8.0 * sz); lfLabelFont.setFontSize (12.0 * sz); // Resize widgets RESIZE (mContainer, 0, 0, 1200, 710, sz); RESIZE (messageLabel, 500, 45, 500, 20, sz); RESIZE (bypassButton, 1090, 15, 16, 16, sz); RESIZE (drywetDial, 1144, 5, 33, 40, sz); RESIZE (midiTriggerSwitch, 760, 568, 30, 12, sz); RESIZE (midiPiano, 760, 585, 150, 30, sz); RESIZE (midiThruSwitch, 1044, 575, 12, 30, sz); RESIZE (baseValueSelect, 480, 660, 100, 20, sz); RESIZE (baseListBox, 620, 660, 100, 20, sz); baseListBox.resizeListBox (BUtilities::Point (100 * sz, 80 * sz)); baseListBox.moveListBox (BUtilities::Point (0, -80 * sz)); RESIZE (monitorContainer, 24, 134, 1152, 352, sz); // monitorHorizon1/2 monitorHorizon1.setFadeoutWidth (64 * sz); monitorHorizon2.setFadeoutWidth (64 * sz); monitorHorizon1.setHeight (352 * sz); monitorHorizon2.setHeight (352 * sz); RESIZE (input1Monitor, 0, 0, 1152, 176, sz); RESIZE (output1Monitor, 0, 0, 1152, 176, sz); RESIZE (input2Monitor, 0, 176, 1152, 176, sz); RESIZE (output2Monitor, 0, 176, 1152, 176, sz); for (int i = 0; i < MAXSHAPES; ++i) { RESIZE (shapeGui[i].tabContainer, 20 + i * 148, 90, 147, 40, sz); RESIZE (shapeGui[i].tabIcon, 0, 12.5, 120, 15, sz); for (int j = 0; j< NRSYMBOLS; ++j) RESIZE (shapeGui[i].tabSymbol[j], 120 + (j % 2) * 14 , 8 + int (j / 2) * 14, 10, 10, sz); if (shapeGui[i].tabMsgBox) { RESIZE ( *shapeGui[i].tabMsgBox, shapeGui[i].tabMsgBox->getPosition().x / this->sz, shapeGui[i].tabMsgBox->getPosition().y / this->sz, shapeGui[i].tabMsgBox->getWidth () / this->sz, shapeGui[i].tabMsgBox->getHeight () / this->sz, sz ); } if (shapeGui[i].tabMsgBoxBg) RESIZE (*shapeGui[i].tabMsgBoxBg, 0, 0, 1200, 710, sz); RESIZE (shapeGui[i].shapeContainer, 20, 130, 1160, 590, sz); RESIZE (shapeGui[i].smoothingLabel, 960, 410, 40, 10, sz); RESIZE (shapeGui[i].smoothingDial, 960, 366, 40, 48, sz); RESIZE (shapeGui[i].targetListBox, 20, 443, 174, 54, sz); shapeGui[i].targetListBox.resizeListBox (BUtilities::Point (174 * sz - 20, 380 * sz)); shapeGui[i].targetListBox.moveListBox (BUtilities::Point (0, -380 * sz)); shapeGui[i].targetListBox.resizeListBoxItems (BUtilities::Point (174 * sz - 20, 54 * sz)); RESIZE (shapeGui[i].drywetLabel, 500, 494, 50, 16, sz); RESIZE (shapeGui[i].drywetDial, 500, 434, 50, 60, sz); RESIZE (shapeGui[i].shapeWidget, 4, 4, 1152, 352, sz); RESIZE (shapeGui[i].toolSelect, 133, 368, 284, 44, sz); shapeGui[i].toolSelect.resizeSelection (44 * sz, 44 * sz); for (int j = 0; j < 4; ++j) RESIZE (shapeGui[i].editWidgets[j], 463 + j * 60, 368, 44, 44, sz); for (int j = 4; j < 7; ++j) RESIZE (shapeGui[i].editWidgets[j], 503 + j * 60, 368, 44, 44, sz); RESIZE (shapeGui[i].gridSelect, 1043, 368, 104, 44, sz); shapeGui[i].gridSelect.resizeSelection (44 * sz, 44 * sz); int methodNr = shapeGui[i].targetListBox.getValue (); for (int j = 0; j < MAXOPTIONWIDGETS; ++j) { int optionNr = methods[methodNr].optionIndexes[j]; if (optionNr != NO_OPT) { if (shapeGui[i].optionWidgets[optionNr]) { switch (options[optionNr].widgetType) { case DIAL_WIDGET: RESIZE ((*shapeGui[i].optionWidgets[optionNr]), 230 + j * 70, 434, 50, 60, sz); RESIZE ((shapeGui[i].optionLabels[optionNr]), 220 + j * 70, 494, 70, 16, sz); break; case POPUP_WIDGET: { BItems::ItemList il = options[optionNr].param.get(); size_t max = 0; for (BItems::Item const& it : il) { if (it.getWidget()) { BWidgets::Label* l = (BWidgets::Label*) it.getWidget(); if (l->getText().size() > max) max = l->getText().size(); } } int w = max * 9 + 20; w = LIMIT (w, 50, 270 - j * 70); int h = (il.size() + 1) * 20; h = LIMIT (h, 20, 400); RESIZE ((*shapeGui[i].optionWidgets[optionNr]), 220 + j * 70, 455, w, 20, sz); ((BWidgets::PopupListBox*) shapeGui[i].optionWidgets[optionNr])->resizeListBox (BUtilities::Point (w * sz, h * sz)); ((BWidgets::PopupListBox*) shapeGui[i].optionWidgets[optionNr])->moveListBox (BUtilities::Point (0, -h * sz)); RESIZE ((shapeGui[i].optionLabels[optionNr]), 220 + j * 70, 494, w, 16, sz); } break; default: break; } } } } } this-> sz = sz; // Update monitor, const std::string& name initMonitors (); updateMonitors (0, MONITORBUFFERSIZE - 1); updateHorizon (); // Apply changes applyChildThemes (); show (); } void BShaprGUI::applyChildThemes () { mContainer.applyTheme (theme); messageLabel.applyTheme (theme); bypassButton.applyTheme (theme); drywetDial.applyTheme (theme); midiTriggerSwitch.applyTheme (theme); midiPiano.applyTheme (theme); midiThruSwitch.applyTheme (theme); baseValueSelect.applyTheme (theme); baseListBox.applyTheme (theme); monitorContainer.applyTheme (theme); input1Monitor.applyTheme (theme); output1Monitor.applyTheme (theme); input2Monitor.applyTheme (theme); output2Monitor.applyTheme (theme); for (unsigned int i = 0; i < MAXSHAPES; ++i) { shapeGui[i].shapeContainer.applyTheme (theme); shapeGui[i].tabContainer.applyTheme (theme); shapeGui[i].tabIcon.applyTheme (theme); for (int j = 0; j< NRSYMBOLS; ++j) shapeGui[i].tabSymbol[j].applyTheme (theme); if (shapeGui[i].tabMsgBox) shapeGui[i].tabMsgBox->applyTheme (theme); if (shapeGui[i].tabMsgBoxBg) shapeGui[i].tabMsgBoxBg->applyTheme (theme); shapeGui[i].smoothingLabel.applyTheme (theme); shapeGui[i].smoothingDial.applyTheme (theme); shapeGui[i].targetListBox.applyTheme (theme); shapeGui[i].drywetLabel.applyTheme (theme); shapeGui[i].drywetDial.applyTheme (theme); shapeGui[i].shapeWidget.applyTheme (theme); shapeGui[i].toolSelect.applyTheme (theme); for (int j = 0; j < 7; ++j) shapeGui[i].editWidgets[j].applyTheme (theme); shapeGui[i].gridSelect.applyTheme (theme); int methodNr = shapeGui[i].targetListBox.getValue (); for (int j = 0; j < MAXOPTIONWIDGETS; ++j) { int optionNr = methods[methodNr].optionIndexes[j]; if (optionNr != NO_OPT) { if (shapeGui[i].optionWidgets[optionNr]) shapeGui[i].optionWidgets[optionNr]->applyTheme (theme); shapeGui[i].optionLabels[optionNr].applyTheme (theme); } } } } void BShaprGUI::onConfigureRequest (BEvents::ExposeEvent* event) { Window::onConfigureRequest (event); resizeGUI (getWidth() / 1200 > getHeight() / 710 ? getHeight() / 710 : getWidth() / 1200); } void BShaprGUI::onCloseRequest (BEvents::WidgetEvent* event) { if (event) { Widget* requestWidget = event->getRequestWidget (); if (requestWidget) { for (int i = 0; i < MAXSHAPES; ++i) { if (requestWidget == ((BWidgets::Widget*)shapeGui[i].tabMsgBox)) { double value = shapeGui[i].tabMsgBox->getValue (); // Yes if (value == 2) deleteShape (i); delete shapeGui[i].tabMsgBox; shapeGui[i].tabMsgBox = nullptr; delete shapeGui[i].tabMsgBoxBg; shapeGui[i].tabMsgBoxBg = nullptr; return; } } Window::onCloseRequest (event); } } } void BShaprGUI::onKeyPressed (BEvents::KeyEvent* event) { if ((event) && (event->getKey() == BDevices::KEY_SHIFT)) { for (int i = 0; i < MAXSHAPES; ++i) { shapeGui[i].shapeWidget.setScrollable (false); } } } void BShaprGUI::onKeyReleased (BEvents::KeyEvent* event) { if ((event) && (event->getKey() == BDevices::KEY_SHIFT)) { for (int i = 0; i < MAXSHAPES; ++i) { shapeGui[i].shapeWidget.setScrollable (true); } } } void BShaprGUI::sendGuiOn () { uint8_t obj_buf[64]; lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf)); LV2_Atom_Forge_Frame frame; LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, urids.ui_on); lv2_atom_forge_pop(&forge, &frame); write_function(controller, CONTROL, lv2_atom_total_size(msg), urids.atom_eventTransfer, msg); } void BShaprGUI::sendGuiOff () { uint8_t obj_buf[64]; lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf)); LV2_Atom_Forge_Frame frame; LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&forge, &frame, 0, urids.ui_off); lv2_atom_forge_pop(&forge, &frame); write_function(controller, CONTROL, lv2_atom_total_size(msg), urids.atom_eventTransfer, msg); } void BShaprGUI::sendShape (size_t shapeNr) { size_t size = shapeGui[shapeNr].shapeWidget.size (); uint8_t obj_buf[4096]; lv2_atom_forge_set_buffer(&forge, obj_buf, sizeof(obj_buf)); // Load shapeBuffer for (unsigned int i = 0; i < size; ++i) { Node node = shapeGui[shapeNr].shapeWidget.getRawNode (i); shapeBuffer[i * 7 + 0] = (float)node.nodeType; shapeBuffer[i * 7 + 1] = (float)node.point.x; shapeBuffer[i * 7 + 2] = (float)node.point.y; shapeBuffer[i * 7 + 3] = (float)node.handle1.x; shapeBuffer[i * 7 + 4] = (float)node.handle1.y; shapeBuffer[i * 7 + 5] = (float)node.handle2.x; shapeBuffer[i * 7 + 6] = (float)node.handle2.y; } // Notify shapeBuffer LV2_Atom_Forge_Frame frame; LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object (&forge, &frame, 0, urids.notify_shapeEvent); lv2_atom_forge_key(&forge, urids.notify_shapeNr); lv2_atom_forge_int(&forge, shapeNr); lv2_atom_forge_key(&forge, urids.notify_shapeData); lv2_atom_forge_vector(&forge, sizeof(float), urids.atom_Float, (uint32_t) (7 * size), &shapeBuffer); lv2_atom_forge_pop(&forge, &frame); write_function (controller, CONTROL, lv2_atom_total_size(msg), urids.atom_eventTransfer, msg); } void BShaprGUI::setController (const int controllerNr, const float value) { if ((controllerNr < 0) || (controllerNr >= NR_CONTROLLERS)) return; if (controllerWidgets[controllerNr]) controllerWidgets[controllerNr]->setValue (value); else { controllers[controllerNr] = value; write_function(controller, CONTROLLERS + controllerNr, sizeof(float), 0, &controllers[controllerNr]); } } void BShaprGUI::deleteShape (const int shapeNr) { if ((shapeNr < 0) || (shapeNr >= MAXSHAPES)) return; // Scan SH_OUTPUT for the last shaper sending data to audio out // Limited backward compatibilty to v0.3.2 or older int lastShape = 0; for (int i = 0; i < MAXSHAPES; ++i) { if (controllers[SHAPERS + i * SH_SIZE + SH_OUTPUT] == AUDIO_OUT) lastShape = i; } // Deletion behind last shape (shouldn't happen) if (shapeNr > lastShape) { // Hide all tabContainers > lastShape for (int i = 0; i < MAXSHAPES; ++i) { if (i <= lastShape) shapeGui[i].tabContainer.show (); else shapeGui[i].tabContainer.hide (); } // Jump to last valid shape if (controllers[ACTIVE_SHAPE] - 1 > lastShape) switchShape (lastShape); // Get sure that SH_INPUT of shape 0 == AUDIO_IN if (controllers[SHAPERS + SH_INPUT] != AUDIO_IN) setController (SHAPERS + SH_INPUT, AUDIO_IN); } // Deletion of last shape else if (shapeNr == lastShape) { // shapeNr == 0: Reset shape if (shapeNr == 0) { // Reset shape 0 controllers setController (SHAPERS + SH_INPUT, AUDIO_IN); setController (SHAPERS + SH_INPUT_AMP, 1.0f); shapeGui[0].targetListBox.setValue (0.0); shapeGui[0].drywetDial.setValue (1.0); setController (SHAPERS + SH_OUTPUT, AUDIO_OUT); setController (SHAPERS + SH_OUTPUT_AMP, 1.0f); for (int j = 0; j < MAXOPTIONS; ++j) { if (shapeGui[0].optionWidgets[j]) shapeGui[0].optionWidgets[j]->setValue (options[j].value); } // Reset shape 0 shapeGui[0].shapeWidget.setDefaultShape (); // Hide all tabContainers >= 1 for (int i = 0; i < MAXSHAPES; ++i) { if (i == 0) shapeGui[i].tabContainer.show (); else shapeGui[i].tabContainer.hide (); } // Jump to shape 0 switchShape (0); } // Otherwise: delete shapeNr else { // Link shapeNr - 1 to audio out; unlink shapeNr setController (SHAPERS + (shapeNr - 1) * SH_SIZE + SH_OUTPUT, AUDIO_OUT); setController (SHAPERS + shapeNr * SH_SIZE + SH_OUTPUT, INTERNAL); // Hide all tabContainers >= shapeNr for (int i = 0; i < MAXSHAPES; ++i) { if (i < shapeNr) shapeGui[i].tabContainer.show (); else shapeGui[i].tabContainer.hide (); } // Jump to last valid shape if (controllers[ACTIVE_SHAPE] - 1 > shapeNr - 1) switchShape (shapeNr - 1); // Get sure that SH_INPUT of shape 0 == AUDIO_IN if (controllers[SHAPERS + SH_INPUT] != AUDIO_IN) setController (SHAPERS + SH_INPUT, AUDIO_IN); } } // Deletion of start or middle shape else { // Shift shapers for (int i = shapeNr; i < lastShape; ++i) { // Copy controllers int destNr = SHAPERS + i * SH_SIZE; int srcNr = SHAPERS + (i + 1) * SH_SIZE; if (i == 0) setController (destNr + SH_INPUT, AUDIO_IN); else setController (destNr + SH_INPUT, OUTPUT + i - 1); setController (destNr + SH_INPUT_AMP, controllers[srcNr + SH_INPUT_AMP]); shapeGui[i].targetListBox.setValue (shapeGui[i + 1].targetListBox.getValue ()); shapeGui[i].drywetDial.setValue (shapeGui[i + 1].drywetDial.getValue ()); if (i == lastShape - 1) setController (destNr + SH_OUTPUT, AUDIO_OUT); else setController (destNr + SH_OUTPUT, controllers[srcNr + SH_OUTPUT]); setController (destNr + SH_OUTPUT_AMP, controllers[srcNr + SH_OUTPUT_AMP]); for (int j = 0; j < MAXOPTIONS; ++j) { if (shapeGui[i].optionWidgets[j] && shapeGui[i + 1].optionWidgets[j]) { shapeGui[i].optionWidgets[j]->setValue (shapeGui[i + 1].optionWidgets[j]->getValue ()); } } // Copy shapes shapeGui[i].shapeWidget = shapeGui [i + 1].shapeWidget; } // Unlink lastShape setController (SHAPERS + lastShape * SH_SIZE + SH_OUTPUT, INTERNAL); // Hide all tabContainers >= lastShape for (int i = 0; i < MAXSHAPES; ++i) { if (i < lastShape) shapeGui[i].tabContainer.show (); else shapeGui[i].tabContainer.hide (); } // Jump to last valid shape if (controllers[ACTIVE_SHAPE] - 1 > lastShape - 1) switchShape (lastShape - 1); // Get sure that SH_INPUT of shape 0 == AUDIO_IN if (controllers[SHAPERS + SH_INPUT] != AUDIO_IN) setController (SHAPERS + SH_INPUT, AUDIO_IN); } updateTabs (); } void BShaprGUI::insertShape (const int shapeNr) { if ((shapeNr < 0) || (shapeNr >= MAXSHAPES - 1)) return; // Scan SH_OUTPUT for the last shaper sending data to audio out // Limited backward compatibilty to v0.3.2 or older int lastShape = 0; for (int i = 0; i < MAXSHAPES; ++i) { if (controllers[SHAPERS + i * SH_SIZE + SH_OUTPUT] == AUDIO_OUT) lastShape = i; } if (lastShape >= MAXSHAPES - 1) return; // Insertion just behind last shape if (shapeNr >= lastShape) { // Init shape lastShape + 1 int destNr = SHAPERS + (lastShape + 1) * SH_SIZE; int srcNr = SHAPERS + lastShape * SH_SIZE; setController (destNr + SH_INPUT, OUTPUT + lastShape); setController (destNr + SH_INPUT_AMP, 1.0f); shapeGui[lastShape + 1].targetListBox.setValue (0.0); shapeGui[lastShape + 1].drywetDial.setValue (1.0); setController (destNr + SH_OUTPUT, AUDIO_OUT); setController (destNr + SH_OUTPUT_AMP, 1.0f); for (int j = 0; j < MAXOPTIONS; ++j) { if (shapeGui[lastShape + 1].optionWidgets[j]) shapeGui[lastShape + 1].optionWidgets[j]->setValue (options[j].value); } // Reset shape shapeGui[lastShape + 1].shapeWidget.setDefaultShape (); // Link lastShape setController (srcNr + SH_OUTPUT, INTERNAL); // Show lastShape + 1 for (int i = 0; i < MAXSHAPES; ++i) { if (i <= lastShape + 1) shapeGui[i].tabContainer.show (); else shapeGui[i].tabContainer.hide (); } switchShape (lastShape + 1); } // Insertion in the middlde else { // Shift shapers for (int i = lastShape; i > shapeNr; --i) { int destNr = SHAPERS + (i + 1) * SH_SIZE; int srcNr = SHAPERS + i * SH_SIZE; setController (destNr + SH_INPUT, OUTPUT + i); setController (destNr + SH_INPUT_AMP, controllers[srcNr + SH_INPUT_AMP]); shapeGui[i + 1].targetListBox.setValue (shapeGui[i].targetListBox.getValue ()); shapeGui[i + 1].drywetDial.setValue (shapeGui[i].drywetDial.getValue ()); setController (destNr + SH_OUTPUT, controllers[srcNr + SH_OUTPUT]); setController (destNr + SH_OUTPUT_AMP, controllers[srcNr + SH_OUTPUT_AMP]); for (int j = 0; j < MAXOPTIONS; ++j) { if (shapeGui[i].optionWidgets[j] && shapeGui[i + 1].optionWidgets[j]) { shapeGui[i + 1].optionWidgets[j]->setValue (shapeGui[i].optionWidgets[j]->getValue ()); } } // Copy shapes shapeGui[i + 1].shapeWidget = shapeGui[i].shapeWidget; } // Init shape widgets shapeNr + 1 int destNr = SHAPERS + (shapeNr + 1) * SH_SIZE; setController (destNr + SH_INPUT, OUTPUT + shapeNr); setController (destNr + SH_INPUT_AMP, 1.0f); shapeGui[shapeNr + 1].targetListBox.setValue (0.0f); shapeGui[shapeNr + 1].drywetDial.setValue (1.0f); setController (destNr + SH_OUTPUT, INTERNAL); setController (destNr + SH_OUTPUT_AMP, 1.0f); // Reset shape shapeNr + 1 shapeGui[shapeNr + 1].shapeWidget.setDefaultShape (); // Show lastShape + 1 for (int i = 0; i < MAXSHAPES; ++i) { if (i <= lastShape + 1) shapeGui[i].tabContainer.show (); else shapeGui[i].tabContainer.hide (); } switchShape (shapeNr + 1); } updateTabs (); } void BShaprGUI::swapShapes (const int source, const int dest) { if ((source < 0) || (dest < 0)) return; // Scan SH_OUTPUT for the last shaper sending data to audio out // Limited backward compatibilty to v0.3.2 or older int lastShape = 0; for (int i = 0; i < MAXSHAPES; ++i) { if (controllers[SHAPERS + i * SH_SIZE + SH_OUTPUT] == AUDIO_OUT) lastShape = i; } if ((source > lastShape) || (dest > lastShape)) return; float value; int destNr = SHAPERS + dest * SH_SIZE; int srcNr = SHAPERS + source * SH_SIZE; value = controllers[destNr + SH_TARGET]; shapeGui[dest].targetListBox.setValue (controllers[srcNr + SH_TARGET]); shapeGui[source].targetListBox.setValue (value); value = controllers[dest + SH_DRY_WET]; shapeGui[dest].drywetDial.setValue (controllers[srcNr + SH_DRY_WET]); shapeGui[source].drywetDial.setValue (value); for (int j = 0; j < MAXOPTIONS; ++j) { if (shapeGui[source].optionWidgets[j] && shapeGui[dest].optionWidgets[j]) { value = shapeGui[dest].optionWidgets[j]->getValue (); shapeGui[dest].optionWidgets[j]->setValue (shapeGui[source].optionWidgets[j]->getValue ()); shapeGui[source].optionWidgets[j]->setValue (value); } } // Swap shapes ShapeWidget shBuffer; shBuffer = shapeGui[dest].shapeWidget; shapeGui[dest].shapeWidget = shapeGui[source].shapeWidget; shapeGui[source].shapeWidget = shBuffer; if (controllers[ACTIVE_SHAPE] - 1 == source) switchShape (dest); else if (controllers[ACTIVE_SHAPE] - 1 == dest) switchShape (source); updateTabs (); } void BShaprGUI::switchShape (const int shapeNr) { if ((shapeNr < 0) || (shapeNr >= MAXSHAPES) || shapeNr == controllers[ACTIVE_SHAPE] - 1) return; int oldShapeNr = LIMIT (controllers[ACTIVE_SHAPE], 1, MAXSHAPES) - 1; shapeGui[oldShapeNr].tabContainer.rename ("tab"); shapeGui[oldShapeNr].tabContainer.applyTheme (theme); shapeGui[oldShapeNr].shapeContainer.hide(); setController (ACTIVE_SHAPE, shapeNr + 1); shapeGui[shapeNr].tabContainer.rename ("activetab"); shapeGui[shapeNr].tabContainer.applyTheme (theme); shapeGui[shapeNr].shapeContainer.show(); updateHorizon (); } void BShaprGUI::valueChangedCallback (BEvents::Event* event) { if ((event) && (event->getWidget ())) { BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget (); float value = widget->getValue (); if (widget->getMainWindow ()) { BShaprGUI* ui = (BShaprGUI*) widget->getMainWindow (); int widgetNr = -1; for (int i = 0; i < NR_CONTROLLERS; ++i) { if (widget == ui->controllerWidgets[i]) { widgetNr = i; break; } } // Controllers (menus, dials, selectors, ...) if (widgetNr >= 0) { if (widgetNr == MIDI_CONTROL) { if (value == 1.0f) ui->midiPiano.show (); else ui->midiPiano.hide (); } else if ((widgetNr == BASE) || (widgetNr == BASE_VALUE)) { ui->controllers[widgetNr] = value; ui->calculateXSteps (); ui->updateHorizon (); } else if (widgetNr >= SHAPERS) { int shapeNr = (widgetNr - SHAPERS) / SH_SIZE; int shapeWidgetNr = (widgetNr - SHAPERS) - shapeNr * SH_SIZE; // Target if (shapeWidgetNr == SH_TARGET) { int nr = LIMIT (value, 0, MAXEFFECTS - 1); // Change transformation ui->shapeGui[shapeNr].shapeWidget.setTransformation (methods[nr].transformFactor, methods[nr].transformOffset); // Set shapeWidget display parameters (limits, unit, prefix, ...) ui->shapeGui[shapeNr].shapeWidget.setScaleParameters ( methods[nr].anchorYPos, methods[nr].anchorValue, methods[nr].ratio ); ui->shapeGui[shapeNr].shapeWidget.setUnit (methods[nr].unit); ui->shapeGui[shapeNr].shapeWidget.setPrefix (methods[nr].prefix); ui->shapeGui[shapeNr].shapeWidget.setLowerLimit (methods[nr].limit.min); ui->shapeGui[shapeNr].shapeWidget.setHigherLimit (methods[nr].limit.max); ui->shapeGui[shapeNr].shapeWidget.update(); // Hide old controllers int oldMethodNr = ui->controllers[widgetNr]; for (int i = 0; i < MAXOPTIONWIDGETS; ++i) { int optionNr = methods[oldMethodNr].optionIndexes[i]; if (optionNr != NO_OPT) { if (ui->shapeGui[shapeNr].optionWidgets[optionNr]) ui->shapeGui[shapeNr].optionWidgets[optionNr]->hide (); ui->shapeGui[shapeNr].optionLabels[optionNr].hide (); } } // Configure and show new controllers int methodNr = value; for (int i = 0; i < MAXOPTIONWIDGETS; ++i) { int optionNr = methods[methodNr].optionIndexes[i]; if (optionNr != NO_OPT) { if (options[optionNr].widgetType != NO_WIDGET) { if (options[optionNr].widgetType == DIAL_WIDGET) { if (ui->shapeGui[shapeNr].optionWidgets[optionNr]) { RESIZE ((*ui->shapeGui[shapeNr].optionWidgets[optionNr]), 230 + i * 70, 434, 50, 60, ui->sz); RESIZE ((ui->shapeGui[shapeNr].optionLabels[optionNr]), 220 + i * 70, 494, 70, 16, ui->sz); } } else if (options[optionNr].widgetType == POPUP_WIDGET) { if (ui->shapeGui[shapeNr].optionWidgets[optionNr]) { BItems::ItemList il = options[optionNr].param.get(); size_t max = 0; for (BItems::Item const& it : il) { if (it.getWidget()) { BWidgets::Label* l = (BWidgets::Label*) it.getWidget(); if (l->getText().size() > max) max = l->getText().size(); } } int w = max * 9 + 20; w = LIMIT (w, 50, 270 - i * 70); int h = (il.size() + 1) * 20; h = LIMIT (h, 20, 400); RESIZE ((*ui->shapeGui[shapeNr].optionWidgets[optionNr]), 220 + i * 70, 455, w, 20, ui->sz); ((BWidgets::PopupListBox*) ui->shapeGui[shapeNr].optionWidgets[optionNr])->resizeListBox (BUtilities::Point (w * ui->sz, h * ui->sz)); ((BWidgets::PopupListBox*) ui->shapeGui[shapeNr].optionWidgets[optionNr])->moveListBox (BUtilities::Point (0, -h * ui->sz)); RESIZE ((ui->shapeGui[shapeNr].optionLabels[optionNr]), 220 + i * 70, 494, w, 16, ui->sz); } } // Show option controller if (ui->shapeGui[shapeNr].optionWidgets[optionNr]) { ui->shapeGui[shapeNr].optionWidgets[optionNr]->applyTheme (ui->theme); ui->shapeGui[shapeNr].optionWidgets[optionNr]->show (); } // Show option label ui->shapeGui[shapeNr].optionLabels[optionNr].applyTheme (ui->theme); //RESIZE ((ui->shapeGui[shapeNr].optionLabels[optionNr]), 220 + i * 70, 494, 60, 16, ui->sz); ui->shapeGui[shapeNr].optionLabels[optionNr].show (); } } } } else if (shapeWidgetNr == SH_SMOOTHING) { if (shapeNr == ui->controllers[ACTIVE_SHAPE] - 1) ui->updateHorizon (); } } ui->controllers[widgetNr] = value; ui->write_function(ui->controller, CONTROLLERS + widgetNr, sizeof(float), 0, &ui->controllers[widgetNr]); } } } } void BShaprGUI::tabClickedCallback (BEvents::Event* event) { if ((event) && (event->getWidget ())) { BWidgets::Widget* widget = (BWidgets::Widget*) event->getWidget (); if (widget->getMainWindow ()) { BShaprGUI* ui = (BShaprGUI*) widget->getMainWindow (); for (int i = 0; i < MAXSHAPES; ++i) { if ((widget == &ui->shapeGui[i].tabIcon) || (widget == &ui->shapeGui[i].tabContainer)) { ui->switchShape (i); break; } else if (widget == &ui->shapeGui[i].tabSymbol[CLOSESYMBOL]) { if (ui->shapeGui[i].tabMsgBox) delete ui->shapeGui[i].tabMsgBox; ui->shapeGui[i].tabMsgBox = nullptr; if (ui->shapeGui[i].tabMsgBoxBg) delete ui->shapeGui[i].tabMsgBoxBg; ui->shapeGui[i].tabMsgBoxBg = nullptr; ui->shapeGui[i].tabMsgBox = new BWidgets::MessageBox (500 * ui->sz, 240 * ui->sz, 200 * ui->sz, 120 * ui->sz, "msgbox", "Delete shape " + std::to_string (i + 1), "Do you really want to delete this shape and all its content and settings ?", {"No", "Yes"}); if (ui->shapeGui[i].tabMsgBox) { ui->shapeGui[i].tabMsgBox->applyTheme (ui->theme); ui->shapeGui[i].tabMsgBoxBg = new BWidgets::Widget (0, 0, 1200 * ui->sz, 710 * ui->sz, "widget"); if (ui->shapeGui[i].tabMsgBoxBg) { ui->shapeGui[i].tabMsgBoxBg->applyTheme (ui->theme); ui->shapeGui[i].tabMsgBoxBg->add (*ui->shapeGui[i].tabMsgBox); ui->mContainer.add (*ui->shapeGui[i].tabMsgBoxBg); } } break; } else if (widget == &ui->shapeGui[i].tabSymbol[ADDSYMBOL]) { ui->insertShape (i); break; } else if (widget == &ui->shapeGui[i].tabSymbol[LEFTSYMBOL]) { ui->swapShapes (i, i - 1); break; } else if (widget == &ui->shapeGui[i].tabSymbol[RIGHTSYMBOL]) { ui->swapShapes (i, i + 1); break; } } } } } void BShaprGUI::shapeChangedCallback (BEvents::Event* event) { if ((event) && (event->getWidget ())) { BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget (); if (widget->getMainWindow () && (widget->getValue () == 1)) { BShaprGUI* ui = (BShaprGUI*) widget->getMainWindow (); for (int i = 0; i < MAXSHAPES; ++i) { if (widget == (BWidgets::ValueWidget*) &ui->shapeGui[i].shapeWidget) { ui->sendShape(i); break; } } } } } void BShaprGUI::toolChangedCallback (BEvents::Event* event) { if ((event) && (event->getWidget ())) { BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget (); if (widget->getMainWindow ()) { BShaprGUI* ui = (BShaprGUI*) widget->getMainWindow (); for (int i = 0; i < MAXSHAPES; ++i) { if (widget == (BWidgets::ValueWidget*) &ui->shapeGui[i].toolSelect) { ui->shapeGui[i].shapeWidget.setTool((ToolType) ui->shapeGui[i].toolSelect.getValue ()); break; } } } } } void BShaprGUI::editClickedCallback (BEvents::Event* event) { if ((event) && (event->getWidget ())) { BWidgets::Widget* widget = (BWidgets::Widget*) event->getWidget (); if (widget->getMainWindow ()) { BShaprGUI* ui = (BShaprGUI*) widget->getMainWindow (); for (int i = 0; i < MAXSHAPES; ++i) { for (int j = 0; j < 7; ++j) { if (widget == &ui->shapeGui[i].editWidgets[j]) { ui->shapeGui[i].editWidgets[j].rename ("frame"); ui->shapeGui[i].editWidgets[j].applyTheme (ui->theme); switch (j) { case 0: ui->clipboard = ui->shapeGui[i].shapeWidget.cutSelection (); return; case 1: ui->clipboard = ui->shapeGui[i].shapeWidget.copySelection (); return; case 2: ui->shapeGui[i].shapeWidget.pasteSelection (ui->clipboard); return; case 3: ui->shapeGui[i].shapeWidget.deleteSelection (); return; case 4: ui->shapeGui[i].shapeWidget.reset (); return; case 5: ui->shapeGui[i].shapeWidget.undo (); return; case 6: ui->shapeGui[i].shapeWidget.redo (); return; default: return; } } } } } } } void BShaprGUI::editReleasedCallback (BEvents::Event* event) { if ((event) && (event->getWidget ())) { BWidgets::Widget* widget = (BWidgets::Widget*) event->getWidget (); if (widget->getMainWindow ()) { BShaprGUI* ui = (BShaprGUI*) widget->getMainWindow (); for (int i = 0; i < MAXSHAPES; ++i) { for (int j = 0; j < 7; ++j) { if (widget == &ui->shapeGui[i].editWidgets[j]) { ui->shapeGui[i].editWidgets[j].rename ("widget"); ui->shapeGui[i].editWidgets[j].applyTheme (ui->theme); } } } } } } void BShaprGUI::gridChangedCallback (BEvents::Event* event) { if ((event) && (event->getWidget ())) { BWidgets::ValueWidget* widget = (BWidgets::ValueWidget*) event->getWidget (); if (widget->getMainWindow ()) { BShaprGUI* ui = (BShaprGUI*) widget->getMainWindow (); for (int i = 0; i < MAXSHAPES; ++i) { if (widget == (BWidgets::ValueWidget*) &ui->shapeGui[i].gridSelect) { int value = ui->shapeGui[i].gridSelect.getValue (); switch (value) { case 0: ui->shapeGui[i].shapeWidget.hideGrid (); ui->shapeGui[i].shapeWidget.setSnap (false); break; case 1: ui->shapeGui[i].shapeWidget.showGrid (); ui->shapeGui[i].shapeWidget.setSnap (false); break; case 2: ui->shapeGui[i].shapeWidget.showGrid (); ui->shapeGui[i].shapeWidget.setSnap (true); break; default:break; } break; } } } } } void BShaprGUI::wheelScrolledCallback (BEvents::Event* event) { if ((event) && (event->getWidget ())) { BEvents::WheelEvent* we = (BEvents::WheelEvent*) event; BWidgets::Widget* widget = event->getWidget (); if (widget->getMainWindow ()) { BShaprGUI* ui = (BShaprGUI*) widget->getMainWindow (); ui->monitorScale = ui->monitorScale * (1 + 0.01 * we->getDelta().y); if (ui->monitorScale < 0.01) ui->monitorScale = 0.01; ui->input1Monitor.setZoom (ui->monitorScale); ui->output1Monitor.setZoom (ui->monitorScale); ui->input2Monitor.setZoom (ui->monitorScale); ui->output2Monitor.setZoom (ui->monitorScale); } } } void BShaprGUI::pianoCallback (BEvents::Event* event) { if ((event) && (event->getWidget ())) { BWidgets::Widget* widget = event->getWidget (); if (widget->getMainWindow ()) { BShaprGUI* ui = (BShaprGUI*) widget->getMainWindow (); std::vector keys = ui->midiPiano.getPressedKeys (); size_t sz = (keys.size () < 12 ? keys.size () : 12); uint32_t bits = 0; for (unsigned int i = 0; i < sz; ++i) { if (keys[i]) bits += (1 << i); } if (bits != uint32_t (ui->controllers[MIDI_KEYS])) { ui->controllers[MIDI_KEYS] = bits; ui->write_function(ui->controller, CONTROLLERS + MIDI_KEYS, sizeof(float), 0, &ui->controllers[MIDI_KEYS]); } } } } void BShaprGUI::calculateXSteps () { majorXSteps = (controllers[BASE_VALUE] != 0.0 ? 1.0 / controllers[BASE_VALUE] : 1.0); switch ((BShaprBaseIndex)controllers[BASE]) { case SECONDS: minorXSteps = majorXSteps / 10.0; break; case BEATS: if (beatUnit != 0) minorXSteps = majorXSteps / (16.0 / ((double)beatUnit)); else minorXSteps = majorXSteps / 4.0; break; case BARS: if (beatsPerBar != 0.0f) minorXSteps = majorXSteps / beatsPerBar; else minorXSteps = majorXSteps / 4.0; break; default: minorXSteps = 1.0; break; } if (controllers[BASE_VALUE] >= 10.0) minorXSteps = majorXSteps; for (int sh = 0; sh < MAXSHAPES; ++sh) { shapeGui[sh].shapeWidget.setMinorXSteps (minorXSteps); shapeGui[sh].shapeWidget.setMajorXSteps (majorXSteps); shapeGui[sh].shapeWidget.update (); } } void BShaprGUI::initMonitors () { input1Monitor.clear (); output1Monitor.clear (); input2Monitor.clear (); output2Monitor.clear (); horizonPos = 0; } std::pair BShaprGUI::translateNotification (BShaprNotifications* notifications, uint32_t notificationsCount) { if (notificationsCount == 0) return std::make_pair (0, 0); int startpos = notifications[0].position; int monitorpos = startpos; for (unsigned int i = 0; i < notificationsCount; ++i) { monitorpos = LIMIT (notifications[i].position, 0, MONITORBUFFERSIZE - 1); input1Monitor.addData (monitorpos, notifications[i].input1); output1Monitor.addData (monitorpos, notifications[i].output1); input2Monitor.addData (monitorpos, notifications[i].input2); output2Monitor.addData (monitorpos, notifications[i].output2); horizonPos = double (monitorpos) / MONITORBUFFERSIZE; } return std::make_pair (startpos, monitorpos); } void BShaprGUI::updateMonitors (int start, int end) { input1Monitor.redrawRange (start, end); output1Monitor.redrawRange (start, end); input2Monitor.redrawRange (start, end); output2Monitor.redrawRange (start, end); } void BShaprGUI::updateHorizon () { double width = monitorContainer.getEffectiveWidth (); int shapeNr = LIMIT (controllers[ACTIVE_SHAPE], 1, MAXSHAPES) - 1; double smTime = shapeGui[shapeNr].smoothingDial.getValue () / 1000; double smWidth; switch (int (controllers[BASE])) { case SECONDS : smWidth = width * smTime / controllers[BASE_VALUE]; break; case BEATS: smWidth = width * smTime * (bpm / 60.0) / controllers[BASE_VALUE]; break; case BARS: smWidth = width * smTime * (bpm / 60.0 / beatsPerBar) / controllers[BASE_VALUE]; break; default: break; } monitorHorizon1.setSmoothingWidth (smWidth); monitorHorizon1.moveLineTo (horizonPos * width, 0); monitorHorizon2.setSmoothingWidth (smWidth); monitorHorizon2.moveLineTo ((horizonPos - 1) * width, 0); } static LV2UI_Handle instantiate (const LV2UI_Descriptor *descriptor, const char *plugin_uri, const char *bundle_path, LV2UI_Write_Function write_function, LV2UI_Controller controller, LV2UI_Widget *widget, const LV2_Feature *const *features) { PuglNativeView parentWindow = 0; LV2UI_Resize* resize = NULL; if (strcmp(plugin_uri, BSHAPR_URI) != 0) { std::cerr << "BShapr.lv2#GUI: GUI does not support plugin with URI " << plugin_uri << std::endl; return NULL; } for (int i = 0; features[i]; ++i) { if (!strcmp(features[i]->URI, LV2_UI__parent)) parentWindow = (PuglNativeView) features[i]->data; else if (!strcmp(features[i]->URI, LV2_UI__resize)) resize = (LV2UI_Resize*)features[i]->data; } if (parentWindow == 0) std::cerr << "BShapr.lv2#GUI: No parent window.\n"; // New instance BShaprGUI* ui; try {ui = new BShaprGUI (bundle_path, features, parentWindow);} catch (std::exception& exc) { std::cerr << "BShapr.lv2#GUI: Instantiation failed. " << exc.what () << std::endl; return NULL; } ui->controller = controller; ui->write_function = write_function; // Reduce min GUI size for small displays double sz = 1.0; int screenWidth = getScreenWidth (); int screenHeight = getScreenHeight (); if ((screenWidth < 1240) || (screenHeight < 720)) sz = 0.66; if ((screenWidth < 840) || (screenHeight < 530)) sz = 0.50; if (resize) resize->ui_resize (resize->handle, 1200 * sz, 710 * sz); *widget = (LV2UI_Widget) puglGetNativeWindow (ui->getPuglView ()); ui->sendGuiOn(); return (LV2UI_Handle) ui; } static void cleanup(LV2UI_Handle ui) { BShaprGUI* pluginGui = (BShaprGUI*) ui; if (pluginGui) delete pluginGui; } static void portEvent(LV2UI_Handle ui, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void* buffer) { BShaprGUI* pluginGui = (BShaprGUI*) ui; if (pluginGui) pluginGui->portEvent(port_index, buffer_size, format, buffer); } static int callIdle (LV2UI_Handle ui) { BShaprGUI* pluginGui = (BShaprGUI*) ui; if (pluginGui) pluginGui->handleEvents (); return 0; } static int callResize (LV2UI_Handle ui, int width, int height) { BShaprGUI* self = (BShaprGUI*) ui; if (!self) return 0; BEvents::ExposeEvent* ev = new BEvents::ExposeEvent (self, self, BEvents::CONFIGURE_REQUEST_EVENT, self->getPosition().x, self->getPosition().y, width, height); self->addEventToQueue (ev); return 0; } static const LV2UI_Idle_Interface idle = {callIdle}; static const LV2UI_Resize resize = {nullptr, callResize}; static const void* extensionData(const char* uri) { if (!strcmp(uri, LV2_UI__idleInterface)) return &idle; else if(!strcmp(uri, LV2_UI__resize)) return &resize; else return NULL; } static const LV2UI_Descriptor guiDescriptor = { BSHAPR_GUI_URI, instantiate, cleanup, portEvent, extensionData }; // LV2 Symbol Export LV2_SYMBOL_EXPORT const LV2UI_Descriptor *lv2ui_descriptor(uint32_t index) { switch (index) { case 0: return &guiDescriptor; default:return NULL; } } BShapr-0.13/src/BShaprGUI.hpp000066400000000000000000000346751405711530000156530ustar00rootroot00000000000000/* B.Shapr * Beat / envelope shaper LV2 plugin * * Copyright (C) 2019 by Sven Jähnichen * * This program 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, or (at your option) * any later version. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef BSHAPRGUI_HPP_ #define BSHAPRGUI_HPP_ #include #include #include #include #include #include #include #include #include #include #include #include "BWidgets/Widget.hpp" #include "BWidgets/Window.hpp" #include "BWidgets/RangeWidget.hpp" #include "BWidgets/ImageIcon.hpp" #include "BWidgets/Label.hpp" #include "BWidgets/Dial.hpp" #include "BWidgets/DialValue.hpp" #include "BWidgets/PopupListBox.hpp" #include "BWidgets/HSwitch.hpp" #include "BWidgets/VSwitch.hpp" #include "BWidgets/HPianoRoll.hpp" #include "BWidgets/MessageBox.hpp" #include "ShapeWidget.hpp" #include "ValueSelect.hpp" #include "HorizonWidget.hpp" #include "MonitorWidget.hpp" #include "SymbolWidget.hpp" #include "EditWidget.hpp" #include "LightButton.hpp" #include "Globals.hpp" #include "Urids.hpp" #include "BShaprNotifications.hpp" #include "screen.h" #include "SelectWidget.hpp" #define BG_FILE "inc/surface.png" #define RESIZE(widget, x, y, w, h, sz) {(widget).moveTo ((x) * (sz), (y) * (sz)); (widget).resize ((w) * (sz), (h) * (sz));} const std::string messageStrings[MAXMESSAGES] = { "", "Msg: Jack transport off or halted.", "Msg: Orphan active shape. Select an input for this shape!", "Msg: Orphan active shape. Select an output for this or a connected shape!" }; const std::string editWidgetLabels[7] = {"Cut", "Copy", "Paste", "Delete", "Reset", "Undo", "Redo"}; class BShaprGUI : public BWidgets::Window { public: BShaprGUI (const char *bundlePath, const LV2_Feature *const *features, PuglNativeView parentWindow); ~BShaprGUI (); void portEvent (uint32_t port, uint32_t bufferSize, uint32_t format, const void *buffer); void sendGuiOn (); void sendGuiOff (); void sendShape (size_t shapeNr); virtual void onConfigureRequest (BEvents::ExposeEvent* event) override; virtual void onCloseRequest (BEvents::WidgetEvent* event) override; virtual void onKeyPressed (BEvents::KeyEvent* event) override; virtual void onKeyReleased (BEvents::KeyEvent* event) override; void applyChildThemes (); LV2UI_Controller controller; LV2UI_Write_Function write_function; static void valueChangedCallback (BEvents::Event* event); static void shapeChangedCallback (BEvents::Event* event); static void toolChangedCallback (BEvents::Event* event); static void editClickedCallback (BEvents::Event* event); static void editReleasedCallback (BEvents::Event* event); static void gridChangedCallback (BEvents::Event* event); static void tabClickedCallback (BEvents::Event* event); static void wheelScrolledCallback (BEvents::Event* event); static void pianoCallback (BEvents::Event* event); private: void setController (const int controllerNr, const float value); void deleteShape (const int shapeNr); void insertShape (const int shapeNr); void swapShapes (const int source, const int dest); void switchShape (const int shapeNr); void resizeGUI (const double sz); void updateTabs (); void calculateXSteps (); void initMonitors (); std::pair translateNotification (BShaprNotifications* notifications, uint32_t notificationsCount); void updateMonitors (int start, int end); void updateHorizon (); // Controllers std::array controllerWidgets; std::array controllers; float bpm; float beatsPerBar; int beatUnit; // Widgets struct ShapeGui { BWidgets::Widget shapeContainer; BWidgets::Widget tabContainer; std::array tabSymbol; BWidgets::Widget* tabMsgBoxBg; BWidgets::MessageBox* tabMsgBox; BWidgets::ImageIcon tabIcon; BWidgets::Label smoothingLabel; BWidgets::DialValue smoothingDial; BWidgets::PopupListBox targetListBox; BWidgets::Label drywetLabel; BWidgets::DialValue drywetDial; ShapeWidget shapeWidget; std::list methodIcons; std::array optionWidgets; std::array optionLabels; SelectWidget toolSelect; std::array editWidgets; SelectWidget gridSelect; } ; BWidgets::ImageIcon mContainer; BWidgets::Label messageLabel; LightButton bypassButton; BWidgets::DialValue drywetDial; BWidgets::HSwitch midiTriggerSwitch; BWidgets::HPianoRoll midiPiano; BWidgets::VSwitch midiThruSwitch; ValueSelect baseValueSelect; BWidgets::PopupListBox baseListBox; BWidgets::Widget monitorContainer; HorizonWidget monitorHorizon1; HorizonWidget monitorHorizon2; MonitorWidget input1Monitor; MonitorWidget output1Monitor; MonitorWidget input2Monitor; MonitorWidget output2Monitor; std::array shapeGui; float shapeBuffer[MAXNODES * 7]; double horizonPos; double monitorScale; double minorXSteps; double majorXSteps; std::vector clipboard; std::string pluginPath; double sz; cairo_surface_t* bgImageSurface; LV2_Atom_Forge forge; BShaprURIDs urids; LV2_URID_Map* map; // Definition of styles BColors::ColorSet fgColors = {{{0.75, 0.0, 0.75, 1.0}, {1.0, 0.0, 1.0, 1.0}, {0.25, 0.0, 0.25, 1.0}, {0.0, 0.0, 0.0, 0.0}}}; BColors::ColorSet rdColors = {{{0.75, 0.0, 0.0, 1.0}, {1.0, 0.25, 0.25, 1.0}, {0.2, 0.0, 0.0, 1.0}, {0.0, 0.0, 0.0, 0.0}}}; BColors::ColorSet txColors = {{{1.0, 1.0, 1.0, 1.0}, {1.0, 1.0, 1.0, 1.0}, {0.2, 0.2, 0.2, 1.0}, {0.0, 0.0, 0.0, 0.0}}}; BColors::ColorSet bgColors = {{{0.15, 0.15, 0.15, 1.0}, {0.3, 0.3, 0.3, 1.0}, {0.05, 0.05, 0.05, 1.0}, {0.0, 0.0, 0.0, 0.0}}}; BColors::ColorSet ltbgColors = {{{0.3, 0.3, 0.3, 1.0}, {0.5, 0.5, 0.5, 1.0}, {0.1, 0.1, 0.1, 1.0}, {0.0, 0.0, 0.0, 0.0}}}; BColors::ColorSet blkColors = {{{0.0, 0.0, 0.0, 1.0}, {0.0, 0.0, 0.0, 1.0}, {0.0, 0.0, 0.0, 1.0}, {0.0, 0.0, 0.0, 0.0}}}; BColors::ColorSet menuBgColors = {{{0.05, 0.05, 0.05, 1.0}, {0.05, 0.05, 0.05, 1.0}, {0.05, 0.05, 0.05, 1.0}, {0.0, 0.0, 0.0, 1.0}}}; BColors::ColorSet buttonBgColors = {{{0.1, 0.1, 0.1, 1.0}, {0.2, 0.2, 0.2, 1.0}, {0.05, 0.05, 0.05, 1.0}, {0.0, 0.0, 0.0, 1.0}}}; BColors::ColorSet clickColors = {{{0.2, 0.2, 0.2, 1.0}, {1, 1, 1, 1.0}, {0, 0, 0, 1.0}, {0, 0, 0, 0.0}}}; BColors::ColorSet ink1 = {{{0.75, 0.0, 1.0, 1.0}, {0.9, 0.5, 1.0, 1.0}, {0.1, 0.0, 0.25, 1.0}, {0.0, 0.0, 0.0, 0.0}}}; BColors::ColorSet ink2 = {{{1.0, 0.0, 0.75, 1.0}, {1.0, 0.5, 0.9, 1.0}, {0.25, 0.0, 0.1, 1.0}, {0.0, 0.0, 0.0, 0.0}}}; BStyles::Border labelBorder = BStyles::Border (BStyles::noLine, 0.0, 4.0); BStyles::Border menuBorder = BStyles::Border (BStyles::Line (BColors::Color (0.2, 0.2, 0.2, 1.0), 1.0)); BStyles::Border screenBorder = BStyles::Border (BStyles::Line (BColors::Color (0.0, 0.0, 0.0, 0.75), 4.0)); BStyles::Border itemBorder = BStyles::Border (BStyles::noLine, 0.0, 2.0); BStyles::Fill tabBg = BStyles::Fill (BColors::Color (0.5, 0, 0.5, 0.5)); BStyles::Fill activeTabBg = BStyles::Fill (BColors::Color (0.5, 0, 0.5, 0.875)); BStyles::Fill screenBg = BStyles::Fill (BColors::Color (0.0, 0.0, 0.0, 0.75)); BStyles::Font defaultFont = BStyles::Font ("Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL, 12.0, BStyles::TEXT_ALIGN_CENTER, BStyles::TEXT_VALIGN_MIDDLE); BStyles::Font smFont = BStyles::Font ("Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL, 10.0, BStyles::TEXT_ALIGN_CENTER, BStyles::TEXT_VALIGN_MIDDLE); BStyles::Font ssmFont = BStyles::Font ("Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL, 8.0, BStyles::TEXT_ALIGN_CENTER, BStyles::TEXT_VALIGN_MIDDLE); BStyles::Font lfLabelFont = BStyles::Font ("Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL, 12.0, BStyles::TEXT_ALIGN_LEFT, BStyles::TEXT_VALIGN_MIDDLE); BStyles::StyleSet defaultStyles = {"default", {{"background", STYLEPTR (&BStyles::noFill)}, {"border", STYLEPTR (&BStyles::noBorder)}}}; BStyles::StyleSet labelStyles = {"labels", {{"background", STYLEPTR (&BStyles::noFill)}, {"border", STYLEPTR (&labelBorder)}, {"textcolors", STYLEPTR (&txColors)}, {"font", STYLEPTR (&lfLabelFont)}}}; BStyles::Theme theme = BStyles::Theme ({ defaultStyles, {"B.Shapr", {{"background", STYLEPTR (&BStyles::blackFill)}, {"border", STYLEPTR (&BStyles::noBorder)}}}, {"widget", {{"uses", STYLEPTR (&defaultStyles)}}}, {"widget/focus", {{"background", STYLEPTR (&screenBg)}, {"border", STYLEPTR (&screenBorder)}, {"textcolors", STYLEPTR (&txColors)}, {"font", STYLEPTR (&smFont)}}}, {"frame", {{"uses", STYLEPTR (&defaultStyles)}, {"border", STYLEPTR (&BStyles::whiteBorder1pt)}}}, {"screen", {{"background", STYLEPTR (&screenBg)}}}, {"icon", {{"uses", STYLEPTR (&defaultStyles)}, {"border", STYLEPTR (&labelBorder)}}}, {"tab", {{"background", STYLEPTR (&tabBg)}, {"border", STYLEPTR (&BStyles::noBorder)}}}, {"activetab", {{"background", STYLEPTR (&activeTabBg)}, {"border", STYLEPTR (&BStyles::noBorder)}}}, {"tool", {{"uses", STYLEPTR (&defaultStyles)}, {"bgcolors", STYLEPTR (&BColors::whites)}}}, {"tool/focus", {{"background", STYLEPTR (&screenBg)}, {"border", STYLEPTR (&screenBorder)}, {"textcolors", STYLEPTR (&txColors)}, {"font", STYLEPTR (&smFont)}}}, {"monitor.in", {{"background", STYLEPTR (&BStyles::blackFill)}, {"border", STYLEPTR (&BStyles::noBorder)}, {"fgcolors", STYLEPTR (&ink1)}}}, {"monitor.out", {{"background", STYLEPTR (&BStyles::blackFill)}, {"border", STYLEPTR (&BStyles::noBorder)}, {"fgcolors", STYLEPTR (&ink2)}}}, {"shape", {{"background", STYLEPTR (&BStyles::noFill)}, {"border", STYLEPTR (&BStyles::noBorder)}, {"fgcolors", STYLEPTR (&BColors::whites)}, {"symbolcolors", STYLEPTR (&fgColors)}, {"font", STYLEPTR (&lfLabelFont)}, {"bgcolors", STYLEPTR (<bgColors)}}}, {"shape/focus", {{"uses", STYLEPTR (&labelStyles)}, {"background", STYLEPTR (&screenBg)}, {"border", STYLEPTR (&screenBorder)}}}, {"redbutton", {{"uses", STYLEPTR (&defaultStyles)}, {"fgcolors", STYLEPTR (&rdColors)}, {"bgcolors", STYLEPTR (&bgColors)}}}, {"dial", {{"uses", STYLEPTR (&defaultStyles)}, {"fgcolors", STYLEPTR (&fgColors)}, {"bgcolors", STYLEPTR (&bgColors)}, {"textcolors", STYLEPTR (&fgColors)}, {"font", STYLEPTR (&defaultFont)}}}, {"dial/focus", {{"background", STYLEPTR (&screenBg)}, {"border", STYLEPTR (&screenBorder)}, {"textcolors", STYLEPTR (&txColors)}, {"font", STYLEPTR (&smFont)}}}, {"symbol", {{"uses", STYLEPTR (&defaultStyles)}, {"fgcolors", STYLEPTR (&blkColors)}}}, {"label", {{"uses", STYLEPTR (&labelStyles)}}}, {"smlabel", {{"uses", STYLEPTR (&defaultStyles)}, {"textcolors", STYLEPTR (&txColors)}, {"font", STYLEPTR (&smFont)}}}, {"ssmlabel", {{"uses", STYLEPTR (&defaultStyles)}, {"textcolors", STYLEPTR (&txColors)}, {"font", STYLEPTR (&ssmFont)}}}, {"select", {{"uses", STYLEPTR (&defaultStyles)}}}, {"select/label", {{"uses", STYLEPTR (&defaultStyles)}, {"textcolors", STYLEPTR (&BColors::whites)}, {"font", STYLEPTR (&defaultFont)}}}, {"select/click", {{"uses", STYLEPTR (&defaultStyles)}, {"bgcolors", STYLEPTR (&clickColors)}}}, {"msgbox", {{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::blackFill)}}}, {"msgbox/title", {{"uses", STYLEPTR (&defaultStyles)}, {"textcolors", STYLEPTR (&BColors::whites)}, {"font", STYLEPTR (&defaultFont)}}}, {"msgbox/text", {{"uses", STYLEPTR (&defaultStyles)}, {"textcolors", STYLEPTR (&BColors::whites)}, {"font", STYLEPTR (&lfLabelFont)}}}, {"msgbox/button", {{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::blackFill)}, {"textcolors", STYLEPTR (&BColors::whites)}, {"font", STYLEPTR (&defaultFont)}, {"bgcolors", STYLEPTR (&buttonBgColors)}}}, {"menu", {{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::blackFill)}}}, {"menu/item", {{"uses", STYLEPTR (&defaultStyles)}, {"border", STYLEPTR (&labelBorder)}, {"textcolors", STYLEPTR (&BColors::whites)}, {"font", STYLEPTR (&lfLabelFont)}}}, {"menu/button", {{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::noFill)}, {"bgcolors", STYLEPTR (&buttonBgColors)}}}, {"menu/listbox", {{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::blackFill)}}}, {"menu/listbox/item", {{"uses", STYLEPTR (&defaultStyles)}, {"border", STYLEPTR (&labelBorder)}, {"textcolors", STYLEPTR (&BColors::whites)}, {"font", STYLEPTR (&lfLabelFont)}}}, {"menu/listbox/button", {{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::blackFill)}, {"bgcolors", STYLEPTR (&buttonBgColors)}}}, {"menu2", {{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::blackFill)}}}, {"menu2/item", {{"uses", STYLEPTR (&defaultStyles)}, {"border", STYLEPTR (&itemBorder)}, {"textcolors", STYLEPTR (&BColors::whites)}, {"font", STYLEPTR (&lfLabelFont)}}}, {"menu2/button", {{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::blackFill)}, {"bgcolors", STYLEPTR (&buttonBgColors)}}}, {"menu2/listbox", {{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::blackFill)}}}, {"menu2/listbox/item", {{"uses", STYLEPTR (&defaultStyles)}, {"border", STYLEPTR (&itemBorder)}, {"textcolors", STYLEPTR (&BColors::whites)}, {"font", STYLEPTR (&lfLabelFont)}}}, {"menu2/listbox/button",{{"border", STYLEPTR (&menuBorder)}, {"background", STYLEPTR (&BStyles::blackFill)}, {"bgcolors", STYLEPTR (&buttonBgColors)}}} }); }; #endif /* BSHAPRGUI_HPP_ */ BShapr-0.13/src/BShaprNotifications.hpp000066400000000000000000000020321405711530000200160ustar00rootroot00000000000000/* B.Shapr * Beat / envelope shaper LV2 plugin * * Copyright (C) 2019 by Sven Jähnichen * * This program 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, or (at your option) * any later version. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef BSHAPRNOTIFICATIONS_HPP_ #define BSHAPRNOTIFICATIONS_HPP_ #include "Range.hpp" struct BShaprNotifications { float position; Range input1; Range output1; Range input2; Range output2; }; #endif /* BSHAPRNOTIFICATIONS_HPP_ */ BShapr-0.13/src/BUtilities/000077500000000000000000000000001405711530000154545ustar00rootroot00000000000000BShapr-0.13/src/BUtilities/Any.hpp000066400000000000000000000050311405711530000167130ustar00rootroot00000000000000/* Any.hpp * Copyright (C) 2019 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BUTILITIES_ANY_HPP_ #define BUTILITIES_ANY_HPP_ #include #include namespace BUtilities { class Any { protected: struct Envelope { virtual ~Envelope () {} virtual Envelope* clone () {return new Envelope (*this);} }; template struct Data : Envelope { Data (const T& t) : data (t) {} virtual ~Data () {} virtual Envelope* clone () override {return new Data (*this);} T data; }; Envelope* dataptr = nullptr; size_t dataTypeHash = typeid (void).hash_code (); Envelope* clone () const { if (dataptr == nullptr) return nullptr; return dataptr->clone (); } public: Any () {} template Any (const T& t) {set (t);} Any (const Any& that) : dataTypeHash (that.dataTypeHash) {dataptr = that.clone ();} ~Any () {if (dataptr) delete dataptr;} Any& operator= (const Any& that) { if (dataptr) delete dataptr; dataptr = that.clone (); dataTypeHash = that.dataTypeHash; return *this; } template void set (const T& t) { if (dataptr) delete dataptr; dataptr = new Data (t); dataTypeHash = typeid (T).hash_code (); } template T get () const { if ((!dataptr) || (typeid (T).hash_code () != dataTypeHash)) return T (); // Return () better throw exception return ((Data*)dataptr)->data; } }; template Any makeAny (const T& t) { Any a; a.set (t); return a; } } #endif /* BUTILITIES_ANY_HPP_ */ BShapr-0.13/src/BUtilities/Path.hpp000066400000000000000000000053101405711530000170600ustar00rootroot00000000000000/* Path.hpp * Copyright (C) 2021 Sven Jähnichen * * This program 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 distributed in the hope that it will be useful, * but WITHOUT PATH WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BUTILITIES_PATH_HPP_ #define BUTILITIES_PATH_HPP_ #include #ifdef _WIN32 #define BUTILITIES_PATH_SLASH "\\" #else #define BUTILITIES_PATH_SLASH "/" #endif namespace BUtilities { class Path { public: Path () : dir_(), file_(), ext_() {} Path (const std::string& path) {split (path);} Path& operator= (const std::string& path) { split (path); return *this; } operator std::string() const {return (dir().empty() || (dir() == BUTILITIES_PATH_SLASH) ? dir() : dir() + BUTILITIES_PATH_SLASH) + filename();} std::string dir() const {return dir_;} std::string filename() const {return file_ + (ext_.empty() ? "" : "." + ext_);} std::string ext() const {return ext_;} protected: std::string dir_; std::string file_; std::string ext_; void split (const std::string& path) { const size_t spos = path.find_last_of (BUTILITIES_PATH_SLASH); if (spos == std::string::npos) dir_ = ""; else if (spos == 0) dir_ = BUTILITIES_PATH_SLASH; else dir_ = path.substr (0, spos); file_ = path.substr (spos + 1); if (file_ == "") ext_ = ""; else if ((file_ == ".") || (file_ == "..")) { dir_ = (dir_.empty() || (dir_ == BUTILITIES_PATH_SLASH) ? dir_ : dir_ + BUTILITIES_PATH_SLASH) + file_; file_ = ""; ext_ = ""; } else { const size_t dpos = file_.find_last_of ("."); if ((dpos != std::string::npos) && (dpos != 0)) { ext_ = file_.substr (dpos + 1); file_ = file_.substr (0, dpos); } else ext_ = ""; } } }; } #endif /* BUTILITIES_PATH_HPP_ */ BShapr-0.13/src/BUtilities/Point.hpp000066400000000000000000000030611405711530000172560ustar00rootroot00000000000000/* Point.hpp * Beat / envelope shaper LV2 plugin * * Copyright (C) 2019 by Sven Jähnichen * * This program 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, or (at your option) * any later version. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef BUTILITIES_POINT_HPP_ #define BUTILITIES_POINT_HPP_ namespace BUtilities { struct Point { double x, y; Point () : Point (0, 0) {} Point (double x, double y) : x (x), y (y) {} Point& operator+= (const Point& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } Point& operator-= (const Point& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } friend bool operator== (const Point& lhs, const Point& rhs) {return ((lhs.x == rhs.x) && (lhs.y == rhs.y));} friend bool operator!= (const Point& lhs, const Point& rhs) {return !(lhs == rhs);} friend Point operator+ (Point lhs, const Point& rhs) {return (lhs += rhs);} friend Point operator- (Point lhs, const Point& rhs) {return (lhs -= rhs);} }; } #endif /* BUTILITIES_POINT_HPP_ */ BShapr-0.13/src/BUtilities/RectArea.hpp000066400000000000000000000075011405711530000176560ustar00rootroot00000000000000/* RectArea.hpp * Beat / envelope shaper LV2 plugin * * Copyright (C) 2019 by Sven Jähnichen * * This program 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, or (at your option) * any later version. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef BUTILITIES_RECTAREA_HPP_ #define BUTILITIES_RECTAREA_HPP_ #include "Point.hpp" namespace BUtilities { class RectArea { protected: Point p1, p2; public: RectArea () : RectArea (Point (), Point ()) {} RectArea (const double x1, const double y1, const double width, const double height) : RectArea (Point (x1, y1), Point (x1 + width, y1 + height)) {} RectArea (const Point& p1, const Point& p2) : p1 (Point ((p1.x < p2.x ? p1.x : p2.x), (p1.y < p2.y ? p1.y : p2.y))), p2 (Point ((p1.x > p2.x ? p1.x : p2.x), (p1.y > p2.y ? p1.y : p2.y))) {} Point getPosition () const {return p1;} double getX () const {return p1.x;} double getY () const {return p1.y;} Point getExtends () const {return Point (p2.x - p1.x, p2.y - p1.y);} double getWidth () const {return (p2.x - p1.x);} double getHeight () const {return (p2.y - p1.y);} void setX (const double x) {moveTo (x, getY());} void setY (const double y) {moveTo (getX(), y);} void moveTo (const double x, const double y) {moveTo (Point (x, y));} void moveTo (const Point& position) { p2 = p2 - p1 + position; p1 = position; } void setWidth (const double width) {resize (width, getHeight());} void setHeight (const double height) {resize (getWidth(), height);} void resize (const double width, const double height) {resize (Point (width, height));} void resize (const Point& extends) {p2 = p1 + extends;} bool contains (const Point& p) const { return ((p.x > p1.x) && (p.x < p2.x) && (p.y > p1.y) && (p.y < p2.y)); } bool includes (const RectArea& ra) const { return ((ra.p1.x >= p1.x) && (ra.p1.y >= p1.y) && (ra.p2.x <= p2.x) && (ra.p2.y <= p2.y)); } bool overlaps (const RectArea& ra) const { return !((ra.p2.x < p1.x) || (ra.p2.y < p1.y) || (ra.p1.x > p2.x) || (ra.p1.y > p2.y)); } void extend (const RectArea& ra) { if (*this == RectArea ()) *this = ra; else if (ra != RectArea ()) { p1 = Point ((p1.x < ra.p1.x ? p1.x : ra.p1.x), (p1.y < ra.p1.y ? p1.y : ra.p1.y)); p2 = Point ((p2.x > ra.p2.x ? p2.x : ra.p2.x), (p2.y > ra.p2.y ? p2.y : ra.p2.y)); } } void intersect (const RectArea& ra) { if ((*this == RectArea ()) || (ra == RectArea ()) || (!overlaps (ra))) *this = RectArea (); else { double x1 = (ra.p1.x < p1.x ? p1.x : ra.p1.x); double y1 = (ra.p1.y < p1.y ? p1.y : ra.p1.y); double x2 = (ra.p2.x > p2.x ? p2.x : ra.p2.x); double y2 = (ra.p2.y > p2.y ? p2.y : ra.p2.y); p1 = Point (x1, y1); p2 = Point (x2, y2); } } RectArea& operator+= (const RectArea& rhs) { this->extend (rhs); return *this; } RectArea& operator*= (const RectArea& rhs) { this->intersect (rhs); return *this; } friend bool operator== (const RectArea& lhs, const RectArea& rhs) { return ((lhs.p1 == rhs.p1) && (lhs.p2 == rhs.p2)); } friend bool operator!= (const RectArea& lhs, const RectArea& rhs) {return !(lhs == rhs);} friend RectArea operator+ (RectArea lhs, const RectArea& rhs) {return (lhs += rhs);} friend RectArea operator* (RectArea lhs, const RectArea& rhs) {return (lhs *= rhs);} }; } #endif /* BUTILITIES_RECTAREA_HPP_ */ BShapr-0.13/src/BUtilities/mix.hpp000066400000000000000000000016731405711530000167710ustar00rootroot00000000000000/* mix.hpp * Copyright (C) 2020 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BUTILITIES_MIX_HPP_ #define BUTILITIES_MIX_HPP_ namespace BUtilities { template inline T mix (const T& t0, const T& t1, const double ratio) { return t1 * ratio + t0 * (1.0f - ratio); } } #endif /* BUTILITIES_MIX_HPP_ */ BShapr-0.13/src/BUtilities/stof.cpp000066400000000000000000000042761405711530000171440ustar00rootroot00000000000000/* stof.cpp * Copyright (C) 2020 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "stof.hpp" #include namespace BUtilities { float stof (const std::string& str, size_t* idx) { const std::string numbers = "0123456789"; bool isNumber = false; float sign = 1.0f; float predec = 0.0f; float dec = 0.0f; float decfac = 0.1f; size_t i = 0; // Ignore spaces before while (str[i] == ' ') ++i; // Check sign if ((str[i] == '+') || (str[i] == '-')) { if (str[i] == '-') sign = -1.0f; ++i; } // Interpret pre-decimal digits while ((str[i] != 0) && (numbers.find_first_of (str[i]) != std::string::npos)) { predec = predec * 10.0f + str[i] - '0'; ++i; isNumber = true; } // Check decimal sign if ((str[i] == '.') || (str[i] == ',')) { ++i; // Interpret decimal digits while ((str[i] != 0) && (numbers.find_first_of (str[i]) != std::string::npos)) { dec += (str[i] - '0') * decfac; decfac *= 0.1f; ++i; isNumber = true; } } // Communicate next position if (idx != nullptr) *idx = i; // Not a number: invalid argument exception if (!isNumber) throw std::invalid_argument (str + " is not a number"); return sign * (predec + dec); } } BShapr-0.13/src/BUtilities/stof.hpp000066400000000000000000000016311405711530000171410ustar00rootroot00000000000000/* stof.hpp * Copyright (C) 2020 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BUTILITIES_STOF_HPP_ #define BUTILITIES_STOF_HPP_ #include #include namespace BUtilities { float stof (const std::string& str, size_t* idx = 0); } #endif /* BUTILITIES_STOF_HPP_ */ BShapr-0.13/src/BUtilities/to_string.cpp000066400000000000000000000020621405711530000201700ustar00rootroot00000000000000/* to_string.cpp * Copyright (C) 2019 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "to_string.hpp" #include namespace BUtilities { std::string to_string (const double value) { std::ostringstream os; os << value; std::string str = os.str(); return str; } std::string to_string (const double value, const std::string& format) { char c[64]; snprintf (c, 64, format.c_str (), value); std::string str = c; return c; } } BShapr-0.13/src/BUtilities/to_string.hpp000066400000000000000000000017251405711530000202020ustar00rootroot00000000000000/* to_string.hpp * Copyright (C) 2019 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BUTILITIES_TO_STRING_HPP_ #define BUTILITIES_TO_STRING_HPP_ #include namespace BUtilities { std::string to_string (const double value); std::string to_string (const double value, const std::string& format); } #endif /* BUTILITIES_TO_STRING_HPP_ */ BShapr-0.13/src/BWidgets/000077500000000000000000000000001405711530000151075ustar00rootroot00000000000000BShapr-0.13/src/BWidgets/BColors.cpp000066400000000000000000000103321405711530000171550ustar00rootroot00000000000000/* BColors.cpp * Copyright (C) 2018 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "BColors.hpp" #ifndef LIMIT #define LIMIT(val, min, max) (val < min ? min : (val < max ? val : max)) #endif namespace BColors { /***************************************************************************** * Class BColors::Color *****************************************************************************/ Color::Color () : Color (0.0, 0.0, 0.0, 0.0) {}; Color::Color (const double red, const double green, const double blue, const double alpha) : red_ (LIMIT (red, 0.0, 1.0)), green_ (LIMIT (green, 0.0, 1.0)), blue_ (LIMIT (blue, 0.0, 1.0)), alpha_ (LIMIT (alpha, 0.0, 1.0)) {} Color::Color (const uint32_t red32, const uint32_t green32, const uint32_t blue32, const uint32_t alpha32) : red_ (red32 / 0xFFFF), green_ (green32 / 0xFFFF), blue_ (blue32 / 0xFFFF), alpha_ (alpha32 / 0xFFFF) {} bool Color::operator== (const Color& that) const {return (0 == compare (that));} bool Color::operator!= (const Color& that) const {return !(0 == compare (that));} void Color::setRGBA (const double red, const double green, const double blue, const double alpha) { red_ = LIMIT (red, 0.0, 1.0); green_ = LIMIT (green, 0.0, 1.0); blue_ = LIMIT (blue, 0.0, 1.0); alpha_ = LIMIT (alpha, 0.0, 1.0); } void Color::setRGB (const double red, const double green, const double blue) { red_ = LIMIT (red, 0.0, 1.0); green_ = LIMIT (green, 0.0, 1.0); blue_ = LIMIT (blue, 0.0, 1.0); } void Color::setAlpha (const double alpha) { alpha_ = LIMIT (alpha, 0.0, 1.0); } double Color::getRed() const {return red_;} double Color::getGreen() const {return green_;} double Color::getBlue() const {return blue_;} double Color::getAlpha() const {return alpha_;} int Color::compare (const Color& that) const { return ((red_ == that.red_) && (green_ == that.green_) && (blue_ == that.blue_) && (alpha_ == that.alpha_) ? 0 : 1); } void Color::applyBrightness (const double brightness) { double b = LIMIT (brightness, -1.0, 1.0); if (b < 0) { red_ = red_ * (b + 1.0); green_ = green_ * (b + 1.0); blue_ = blue_ * (b + 1.0); } else if (b > 0) { red_ = red_ + (1.0 - red_) * b; green_ = green_ + (1.0 - green_) * b; blue_ = blue_ + (1.0 - blue_) * b; } } /* * End of class BColors::Color *****************************************************************************/ /***************************************************************************** * Class BColors::ColorSet *****************************************************************************/ ColorSet::ColorSet () : ColorSet ({grey, lightgrey, darkgrey}) {}; ColorSet::ColorSet (const std::vector vectorOfColors) : colors (vectorOfColors) {}; bool ColorSet::operator== (const ColorSet& that) const {return (colors == that.colors);} bool ColorSet::operator!= (const ColorSet& that) const {return (colors != that.colors);} int ColorSet::compare (const ColorSet& that) const {return (colors == that.colors ? 0 : 1);} void ColorSet::addColor (const State state, const Color& color) { // Filling undefined vector elements with Color invisible int size = colors.size (); for (int i = size; i <= (int) state; ++i) colors.push_back (invisible); colors[state] = color; } void ColorSet::removeColor (const State state) { if (state < colors.size ()) { colors[state] = invisible; } // TODO shrink vector colors if last element is removed } Color* ColorSet::getColor (const State state) { if (state < colors.size ()) { return &colors[state]; } else { return &noColor; } } /* * End of class BColors::ColorSet *****************************************************************************/ } BShapr-0.13/src/BWidgets/BColors.hpp000066400000000000000000000150071405711530000171660ustar00rootroot00000000000000/* BColors.cpp * Copyright (C) 2018, 1019 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BCOLORS_HPP_ #define BCOLORS_HPP_ #include #include #include #ifndef LIMIT #define LIMIT(val, min, max) (val < min ? min : (val < max ? val : max)) #endif /* LIMIT */ #define CAIRO_RGBA(col) (col).getRed(), (col).getGreen(), (col).getBlue(), (col).getAlpha() namespace BColors { /** * Color states */ typedef enum { NORMAL = 0, ACTIVE = 1, INACTIVE = 2, OFF = 3, USER_DEFINED = 4 } State; /** * Class BColors::Color * * Color management class. Uses relative (0.0 .. 1.0) color and alpha values */ class Color { public: Color (); Color (const double red, const double green, const double blue, const double alpha); Color (const uint32_t red32, const uint32_t green32, const uint32_t blue32, const uint32_t alpha32); bool operator== (const Color& that) const; bool operator!= (const Color& that) const; /** * Sets colors rgb and alpha of a BColors::Color * @param red, green, blue, alpha Relative values (0.0 .. 1.0) */ void setRGBA (const double red, const double green, const double blue, const double alpha); /** * Sets colors rgb of a BColors::Color * @param red, green, blue Relative values (0.0 .. 1.0) */ void setRGB (const double red, const double green, const double blue); /** * Sets alpha value of a BColors::Color * @param alpha Relative value (0.0 .. 1.0) */ void setAlpha (const double alpha); /** * Gets red value of a BColors::Color * @return Relative red value (0.0 .. 1.0) */ double getRed () const; /** * Gets green value of a BColors::Color * @return Relative green value (0.0 .. 1.0) */ double getGreen () const; /** * Gets blue value of a BColors::Color * @return Relative blue value (0.0 .. 1.0) */ double getBlue () const; /** * Gets alpha value of a BColors::Color * @return Relative alpha value (0.0 .. 1.0) */ double getAlpha () const; /** * Compares the color of this object with the color of the given object by * comparison of all RGB and alpha values. * @param that Color to compare with * @return 0, if both colors are equal * 1, if the two colors are not equal */ int compare (const Color& that) const; /** * Changes the red, green and blue values of the BColors::Color by a given * brightness. * @param brightness Brightness ranging from -1.0 (full darkness => black * over 0.0 (normal => unchanged) to 1.0 (full brightness * => white) */ void applyBrightness (const double brightness); private: double red_, green_, blue_, alpha_; }; /* * End of class BColors::Color *****************************************************************************/ const Color white = Color (1.0, 1.0, 1.0, 1.0); const Color black = Color (0.0, 0.0, 0.0, 1.0); const Color red = Color (1.0, 0.0, 0.0, 1.0); const Color green = Color (0.0, 1.0, 0.0, 1.0); const Color blue = Color (0.0, 0.0, 1.0, 1.0); const Color yellow = Color (1.0, 1.0, 0.0, 1.0); const Color grey = Color (0.5, 0.5, 0.5, 1.0); const Color lightred = Color (1.0, 0.5, 0.5, 1.0); const Color darkred = Color (0.5, 0.0, 0.0, 1.0); const Color lightgreen = Color (0.5, 1.0, 0.5, 1.0); const Color darkgreen = Color (0.0, 0.5, 0.0, 1.0); const Color lightblue = Color (0.5, 0.5, 1.0, 1.0); const Color darkblue = Color (0.0, 0.0, 0.5, 1.0); const Color lightgrey = Color (0.75, 0.75, 0.75, 1.0); const Color darkgrey = Color (0.25, 0.25, 0.25, 1.0); const Color darkdarkgrey = Color (0.1, 0.1, 0.1, 1.0); const Color grey80 = Color (0.8, 0.8, 0.8, 1.0); const Color grey60 = Color (0.6, 0.6, 0.6, 1.0); const Color grey40 = Color (0.4, 0.4, 0.4, 1.0); const Color grey20 = Color (0.2, 0.2, 0.2, 1.0); const Color invisible = Color (0.0, 0.0, 0.0, 0.0); /** * Class BColors::ColorSet * * Defines a set of colors for different states. I can be used in combination * with BColors::Style. */ class ColorSet { public: ColorSet (); ColorSet (const std::vector vectorOfColors); bool operator== (const ColorSet& that) const; bool operator!= (const ColorSet& that) const; /** * Compares the colorset of this object with the color of the given object by * comparison of all colors of this set. * @param that Colorset to compare with * @return 0, if both sets (content and order) are equal * 1, if the two sets (content and order) are not equal */ int compare (const ColorSet& that) const; /** * Adds (or overwrites) a BColors::Color to the ColorSet * @param state BColors::State of the color to be added to the set. The * set will be extended automatically if needed. * @param color BColors::Color to be added */ void addColor (const State state, const Color& color); /** * Removes a BColors::Color from the ColorSet * @param state BColors::State of the color to be removed from the set. The * set will be shrinked automatically if needed. */ void removeColor (const State state); /** * Gets a (pointer to) BColors::Color from the ColorSet * @param state BColors::State of the color to be returned. * @return Pointer to BColors::Color of the respective state or pointer to * a copy of BColors::invisible if the states color is not within * the ColorSet. */ Color* getColor (const State state); private: std::vector colors; Color noColor = invisible; }; /* * End of class BColors::ColorSet *****************************************************************************/ const ColorSet reds = ColorSet ({red, lightred, darkred, black}); const ColorSet greens = ColorSet ({green, lightgreen, darkgreen, black}); const ColorSet blues = ColorSet ({blue, lightblue, darkblue, black}); const ColorSet greys = ColorSet ({grey, lightgrey, darkgrey, black}); const ColorSet whites = ColorSet ({lightgrey, white, grey, black}); const ColorSet darks = ColorSet ({darkgrey, grey, darkdarkgrey, black}); const ColorSet lights = ColorSet ({lightgrey, white, grey, darkgrey}); } #endif /* BCOLORS_HPP_ */ BShapr-0.13/src/BWidgets/BDevices.hpp000066400000000000000000000174141405711530000173130ustar00rootroot00000000000000/* BDevices.hpp * Copyright (C) 2019 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BWIDGETS_BDEVICES_HPP_ #define BWIDGETS_BDEVICES_HPP_ #include #include #include #include #include "../BUtilities/Point.hpp" namespace BWidgets { class Widget; // Forward declaration } namespace BDevices { /** * Enumeration of mouse buttons as input device for event handling */ enum ButtonCode { NO_BUTTON = 0, LEFT_BUTTON = 1, MIDDLE_BUTTON = 2, RIGHT_BUTTON = 3, NR_OF_BUTTONS = 4 }; enum KeyCode { KEY_F1 = 0xE000, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_PAGE_UP, KEY_PAGE_DOWN, KEY_HOME, KEY_END, KEY_INSERT, KEY_SHIFT, KEY_CTRL, KEY_ALT, KEY_SUPER }; /** * Class BDevices::DeviceGrab * * Links a BWidgets::Widget to a std::set of devices */ template class DeviceGrab { protected: BWidgets::Widget* widget_; std::set devices_; public: DeviceGrab () : DeviceGrab (nullptr, std::set {}) {} DeviceGrab (BWidgets::Widget* widget) : DeviceGrab (widget, std::set {}) {} DeviceGrab (BWidgets::Widget* widget, const T& device) : DeviceGrab (widget, std::set {device}) {} DeviceGrab (BWidgets::Widget* widget, const std::set& devices) : widget_ (widget), devices_ (devices) {} /* Get (the pointer to) the widget of this DeviceGrab * @return Pointer to widget */ BWidgets::Widget* getWidget () const {return widget_;} /* Gets the devices of this DeviceGrab * @return std::set of devices */ std::set getDevices () const {return devices_;} /* Gets infomation whether this DeviceGrab contains a given device or * not. * @param device of device * @return True if device is in this DeviceGrab, otherwise * false */ bool contains (const T& device) const { if (devices_.empty()) return true; // Empty devices_ used as joker return (devices_.find (device) != devices_.end()); } }; template class DeviceGrabStack : std::list> { protected: std::list> stack_; bool contains (BWidgets::Widget* widget) { for (typename std::list>::iterator it = stack_.begin(); it != stack_.end(); ++it) { DeviceGrab& dg = *it; if (dg.getWidget() == widget) return true; } return false; } std::set getDevices (BWidgets::Widget* widget) { std::set devices {}; for (typename std::list>::iterator it = stack_.begin(); it != stack_.end(); ++it) { DeviceGrab& dg = *it; if (dg.getWidget() == widget) { std::set d = dg.getDevices (); devices.insert (d.begin(), d.end()); } } return devices; } public: using std::list>::clear; /* Removes DeviceGrab devices from the stack. If the DeviceGrab devices * are completely depleted, the empty DeviceGrab is removed from the * stack. * @param widget All DeviceGrabs with this widget will be removed * from the stack. * @param device Devices device from all DeviceGrabs of the stack * will be removed. * @param devices All given devices will be removed from all * DeviceGrabs of the stack. * @param deviceGrab For all DeviceGrabs of the stack that match with * deviceGrab.widget all deviceGrab.devices will be * removed. */ void remove (BWidgets::Widget* widget) {remove (DeviceGrab (widget));} void remove (const T& device) {remove (DeviceGrab (nullptr, device));} void remove (const std::set& devices) {remove (DeviceGrab (nullptr, devices));} void remove (const DeviceGrab& deviceGrab) { bool done = true; std::set devices = deviceGrab.getDevices(); BWidgets::Widget* widget = deviceGrab.getWidget(); do { done = true; for (typename std::list>::iterator it = stack_.begin(); it != stack_.end(); ++it) { DeviceGrab& dg = *it; if ((!widget) || (dg.getWidget() == widget)) { // Erase list item if joker (std::set{}) used if (devices.empty()) { stack_.erase (it); done = false; break; } std::set stackDevices = dg.getDevices(); // Deletion of individual devices is not allowed // if joker (std::set{}) is set if (stackDevices.empty()) {} else { // Build difference std::set diff = {}; std::set_difference ( stackDevices.begin(), stackDevices.end(), devices.begin(), devices.end(), std::inserter (diff, diff.begin()) ); // Erase list item if all devices are deleted if (diff.empty()) { stack_.erase (it); done = false; break; } // Otherwise replace list item else dg = DeviceGrab (dg.getWidget(), diff); } } } } while (!done); } /* Adds a widget to the top of DeviceGrab stack. If the widget is * already inside the stack, it is moved to the top and the linked * devices are combined. * @param deviceGrab DeviceGrab */ void add (BWidgets::Widget* widget) {add (DeviceGrab (widget, std::set{}));} void add (const DeviceGrab& deviceGrab) { BWidgets::Widget* widget = deviceGrab.getWidget(); std::set devices = deviceGrab.getDevices (); if (contains(widget)) { std::set d2 = getDevices (widget); if (devices.empty() || d2.empty()) devices.clear(); else devices.insert (d2.begin(), d2.end()); remove (deviceGrab.getWidget()); } stack_.push_back (DeviceGrab (widget, devices)); } /* Gets (the pointer to) the DeviceGrab containing the respective * device. Starts from the top of the DeviceGrab stack. * @param device of the respective device. * @return Pointer to the respective DeviceGrab or nullptr. */ DeviceGrab* getGrab (const T& device) { for (typename std::list>::reverse_iterator rit = stack_.rbegin (); rit != stack_.rend (); ++rit) { DeviceGrab& dg = *rit; if (dg.contains (device)) return &dg; } return nullptr; } }; class MouseDevice { public: ButtonCode button; BUtilities::Point position; protected: std::chrono::steady_clock::time_point time_; public: MouseDevice () : MouseDevice (NO_BUTTON, BUtilities::Point ()) {} MouseDevice (const ButtonCode but) : MouseDevice (but, BUtilities::Point ()) {} MouseDevice (const ButtonCode but, const BUtilities::Point& pos) : button (but), position (pos), time_ (std::chrono::steady_clock::now()) {} std::chrono::steady_clock::time_point getTime () const {return time_;} friend inline bool operator< (const MouseDevice& lhs, const MouseDevice& rhs) { if( lhs.button < rhs.button ) return true; return false; } friend inline bool operator== (const MouseDevice& lhs, const MouseDevice& rhs) { if( lhs.button == rhs.button ) return true; return false; } friend inline bool operator> (const MouseDevice& lhs, const MouseDevice& rhs) {return rhs < lhs;} friend inline bool operator<=(const MouseDevice& lhs, const MouseDevice& rhs) {return !(lhs > rhs);} friend inline bool operator>=(const MouseDevice& lhs, const MouseDevice& rhs) {return !(lhs < rhs);} friend inline bool operator!=(const MouseDevice& lhs, const MouseDevice& rhs) {return !(lhs==rhs);} }; } #endif /*BWIDGETS_BDEVICES_HPP_*/ BShapr-0.13/src/BWidgets/BEvents.hpp000066400000000000000000000341141405711530000171710ustar00rootroot00000000000000/* BEvents.hpp * Copyright (C) 2018, 2019 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BEVENTS_HPP_ #define BEVENTS_HPP_ #include #include #include "BDevices.hpp" #include "../BUtilities/Any.hpp" #include "../BUtilities/RectArea.hpp" namespace BWidgets { class Widget; // Forward declaration } namespace BEvents { //TODO switch toward three pointer event handling states: pass, handle, block /** * Enumeration of event types */ enum EventType { CONFIGURE_REQUEST_EVENT, EXPOSE_REQUEST_EVENT, CLOSE_REQUEST_EVENT, KEY_PRESS_EVENT, KEY_RELEASE_EVENT, BUTTON_PRESS_EVENT, BUTTON_RELEASE_EVENT, BUTTON_CLICK_EVENT, POINTER_MOTION_EVENT, POINTER_DRAG_EVENT, WHEEL_SCROLL_EVENT, VALUE_CHANGED_EVENT, FOCUS_IN_EVENT, FOCUS_OUT_EVENT, MESSAGE_EVENT, NO_EVENT }; /** * Class BEvents::Event * * Main class of events. Only contains the event type and (a pointer to) the * widget which caused the event. All other event classes are derived from this * class. */ class Event { protected: BWidgets::Widget* eventWidget; EventType eventType; public: Event () : Event (nullptr, NO_EVENT) {} Event (BWidgets::Widget* widget, const EventType type) : eventWidget (widget), eventType (type) {} virtual ~Event () {} /** * Gets a pointer to the widget which caused the event. * @return Pointer to the widget */ BWidgets::Widget* getWidget () {return eventWidget;} /** * Gets the type of the event * @return Event type */ EventType getEventType () const {return eventType;} }; /* * End of class BEvents::Event *****************************************************************************/ /** * Class BEvents::WidgetEvent * * Widget events are emitted by an (event) widget if the widget is requested * by an other (request) widget. This event class is typically used if opening * or closing of a window or an request widget is requested. */ class WidgetEvent : public Event { protected: BWidgets::Widget* requestWidget; public: WidgetEvent () : WidgetEvent (nullptr, nullptr, NO_EVENT) {} WidgetEvent (BWidgets::Widget* eventWidget, BWidgets::Widget* requestWidget, const EventType type) : Event (eventWidget, type), requestWidget (requestWidget) {} /** * Gets a pointer to the widget which request the event. * @return Pointer to the widget */ BWidgets::Widget* getRequestWidget () {return requestWidget;} }; /* * End of class BEvents::WidgetEvent *****************************************************************************/ /** * Class BEvents::ExposeEvent * * Expose events are emitted by a parent event widget (or window) if the visual * output of a child (request) widget is requested to be * updated. An expose event additionally contains the coordinates (x, y, width * and height) of the output region (relative to the widgets origin) that should * be updated. */ class ExposeEvent : public WidgetEvent { protected: BUtilities::RectArea exposeArea; public: ExposeEvent () : ExposeEvent (nullptr, nullptr, NO_EVENT, 0, 0, 0, 0) {}; ExposeEvent (BWidgets::Widget* eventWidget, BWidgets::Widget* requestWidget, const EventType type, const double x, const double y, const double width, const double height) : ExposeEvent (eventWidget, requestWidget, type, BUtilities::RectArea (x, y, width, height)) {} ExposeEvent (BWidgets::Widget* eventWidget, BWidgets::Widget* requestWidget, const EventType type, const BUtilities::RectArea& area) : WidgetEvent (eventWidget, requestWidget, type), exposeArea (area) {} /** * Redefines the area coordinates of the output region for the expose * event * @param area Area coordinates relative to the widgets origin */ void setArea (const BUtilities::RectArea& area) {exposeArea = area;} /** * Gets the area coordinates of the output region for the expose event * @return Area coordinates relative to the widgets origin */ BUtilities::RectArea getArea () const {return exposeArea;} }; /* * End of class BEvents::ExposeEvent *****************************************************************************/ /** * Class BEvents::KeyEvent * * Key events are emitted by the system if a key is pressed or released. */ class KeyEvent : public Event { protected: BUtilities::Point point; uint32_t key; public: KeyEvent () : KeyEvent (nullptr, NO_EVENT, 0, 0, 0) {} KeyEvent (BWidgets::Widget* widget, const EventType type, const double x, const double y, const uint32_t unicode) : KeyEvent (widget, type, BUtilities::Point (x, y), unicode) {} KeyEvent (BWidgets::Widget* widget, const EventType type, const BUtilities::Point& position, const uint32_t unicode) : Event (widget, type), point (position), key (unicode) {} /** * Redefines the point coordinates of the key event * @param coords Point coordinates relative to the widgets origin */ void setPosition (const BUtilities::Point& coords) {point = coords;} /** * Gets the point coordinates of the key event * @return coords Point coordinates relative to the widgets origin */ BUtilities::Point getPosition () const {return point;} /** * Gets the key that caused of the key event * @return Unicode of the key */ uint32_t getKey () const {return key;} std::string getKeyUTF8 () const { // Invalide unicode if (key > 0x0010ffff) return ""; std::string s = ""; // 7 bit ASCII: utf-8 = unicode if (key < 0x80) s += char (key); // 2/3/4(/5/6) byte utf-8 else { uint32_t steps = 2; for (uint32_t i = 3; i <= 6; ++i) { if (key >= (uint32_t (2) << (5 * (i - 1)))) steps = i; } char c = char ((0xFF & (0xFF << (8 - steps))) | (key >> (6 * (steps - 1)))); s += c; for (uint32_t i = steps - 1; i >= 1; --i) { char c = char (0x80 | ((key >> (6 * (i - 1))) & 0x3f)); s += c; } } return s; } }; /* * End of class BEvents::KeyEvent *****************************************************************************/ /** * Class BEvents::PointerEvent * * Pointer events are emitted by the system (via pugl and the main window) if * buttons are pressed or released and/or the pointer is moved over a widget. * The pointer event contains data about the position (relative to the * respective widget and the button pressed (or not). * Pointer events will be handled by the respective widget and can be * redirected to external callback functions. */ class PointerEvent : public Event { protected: BUtilities::Point point, origin, delta; BDevices::ButtonCode buttonNr; public: PointerEvent () : PointerEvent (nullptr, NO_EVENT, 0, 0, 0, 0, 0, 0, BDevices::NO_BUTTON) {} PointerEvent (BWidgets::Widget* widget, const EventType type, const double x, const double y, const double xOrigin, const double yOrigin, const double deltaX, const double deltaY, const BDevices::ButtonCode button) : PointerEvent (widget, type, BUtilities::Point (x, y), BUtilities::Point (xOrigin, yOrigin), BUtilities::Point (deltaX, deltaY), button) {} PointerEvent (BWidgets::Widget* widget, const EventType type, const BUtilities::Point& point, const BUtilities::Point& origin, const BUtilities::Point& delta, const BDevices::ButtonCode button) : Event (widget, type), point (point), origin (origin), delta (delta), buttonNr (button) {} /** * Redefines the point coordinate of the pointer event * @param pont Point coordinate relative to the widgets origin */ void setPosition (const BUtilities::Point& coords) {point = coords;} /** * Gets the point coordinate of the pointer event * @return Point coordinate relative to the widgets origin */ BUtilities::Point getPosition () const {return point;} /** * Redefines the point coordinate of the position where the button was * initially pressed * @param origin Point coordinate relative to the widgets origin */ void setOrigin (const BUtilities::Point& coords) {origin = coords;} /** * Gets the point coordinate of the pointer position where the respective * button was initially pressed. The returned value is the same as * for getPoint() for BUTTON_PRESS_EVENTs, 0.0 for POINTER_MOTION_EVENTs. * @return Point coordinate relative to the widgets origin */ BUtilities::Point getOrigin () const {return origin;} /** * Redefines the pointers movement * @param delta Movement of the pointer */ void setDelta (const BUtilities::Point& coords) {delta = coords;} /** * Gets the movement (relative to the last PointerEvent) * @return Change in coordinates */ BUtilities::Point getDelta () const {return delta;} /** * Redefines the button pressed of the pointer event * @param button Button pressed */ void setButton (const BDevices::ButtonCode button) {buttonNr = button;} /** * Gets the button pressed of the pointer event * @return Button pressed */ BDevices::ButtonCode getButton () const {return buttonNr;} }; /* * End of class BEvents::PointerEvent *****************************************************************************/ /** * Class BEvents::WheelEvent * * Wheel events are emitted by the system (via pugl and the main window) if * a (mouse) wheel is turned. * The wheel event contains data about the relative change of the wheel and * about the pointer position (relative to the respective widget. * Wheel events will be handled by the respective widget and can be * redirected to external callback functions. */ class WheelEvent : public Event { protected: BUtilities::Point point; BUtilities::Point delta; public: WheelEvent () : WheelEvent (nullptr, NO_EVENT, 0, 0, 0, 0) {} WheelEvent (BWidgets::Widget* widget, const EventType type, const double x, const double y, const double deltaX, const double deltaY) : WheelEvent (widget, type, BUtilities::Point (x, y), BUtilities::Point (deltaX, deltaY)) {} WheelEvent (BWidgets::Widget* widget, const EventType type, const BUtilities::Point& point, const BUtilities::Point delta) : Event (widget, type), point (point), delta (delta) {} /** * Redefines the pointers coordinate * @param x Point coordinate relative to the widgets origin */ void setPosition (const BUtilities::Point& coords) {point = coords;} /** * Gets the pointers coordinate of the wheel event * @return Point coordinate relative to the widgets origin */ BUtilities::Point getPosition () const {return point;} /** * Redefines the wheels movement * @param delta Movement of the wheel */ void setDelta (const BUtilities::Point& coords) {delta = coords;} /** * Gets the xmovement of the wheel * @return Change in coordinate */ BUtilities::Point getDelta () const {return delta;} }; /* * End of class BEvents::WheelEvent *****************************************************************************/ /** * Class BEvents::ValueChangedEvent * * Value changed events are emitted by widgets (namely BWidgets::ValueWidget) * if their setValue method is called. The event additionally exposes the * changed value (that should also be accessible via * BWidgets::ValueWidget::getValue ()). Value changed events can be handled * internally (e.g., by composite widgets) and can also be redirected to * external callback functions. */ class ValueChangedEvent : public Event { protected: double value; public: ValueChangedEvent () : ValueChangedEvent (nullptr, 0.0) {} ValueChangedEvent (BWidgets::Widget* widget, const double val) : Event (widget, VALUE_CHANGED_EVENT), value (val) {} /** * Redefines the value exposed by the event. This method doesn't change the * value within a widget! * @param val New value */ void setValue (const double val) {value = val;} /** * Gets the value exposed by the event * @return Value of the event */ double getValue () const {return value;} }; /* * End of class BEvents::ValueChangedEvent *****************************************************************************/ /** * Class BEvents::FocusEvent * * Focus events are emitted by widgets if the pointer rests for a predefined * time over the widget */ class FocusEvent : public Event { protected: BUtilities::Point point; public: FocusEvent () : FocusEvent (nullptr, NO_EVENT, 0, 0) {} FocusEvent (BWidgets::Widget* widget, const EventType type, const double x, const double y) : FocusEvent (widget, type, BUtilities::Point (x, y)) {} FocusEvent (BWidgets::Widget* widget, const EventType type, const BUtilities::Point& point) : Event (widget, type), point (point) {} /** * Redefines the pointers coordinate * @param x Point coordinate relative to the widgets origin */ void setPosition (const BUtilities::Point& coords) {point = coords;} /** * Gets the pointers coordinate of the wheel event * @return Point coordinate relative to the widgets origin */ BUtilities::Point getPosition () const {return point;} }; /* * End of class BEvents::ValueChangedEvent *****************************************************************************/ /** * Class BEvents::MessageEvent * * Ubiquitous event type */ class MessageEvent : public Event { protected: std::string messageName; BUtilities::Any messageContent; public: MessageEvent () : MessageEvent (nullptr, "", BUtilities::Any ()) {} MessageEvent (BWidgets::Widget* widget, const std::string& name, const BUtilities::Any& content) : Event (widget, MESSAGE_EVENT), messageName (name), messageContent (content) {} void setName (const std::string& name) {messageName = name;} std::string getName () const {return messageName;} void setContent (const BUtilities::Any& content) {messageContent = content;} BUtilities::Any getContent () const {return messageContent;} }; } #endif /* BEVENTS_HPP_ */ BShapr-0.13/src/BWidgets/BItems.cpp000066400000000000000000000116131405711530000170000ustar00rootroot00000000000000/* BItems.cpp * Copyright (C) 2019 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "BItems.hpp" #include "Label.hpp" #include namespace BItems { Item::Item () : Item (UNSELECTED, nullptr) {} Item::Item (const double value, BWidgets::Widget* widget) : value (value), widget (widget), internal (nullptr) {} Item::Item (const double value, const std::string& text) : Item (value, nullptr) { try {internal = new BWidgets::Label (0, 0, BWIDGETS_DEFAULT_LABEL_WIDTH, BWIDGETS_DEFAULT_LABEL_HEIGHT, "label", text);} catch (std::bad_alloc &ba) {throw ba;} internal->getBorder ()->setPadding (BITEMS_DEFAULT_TEXT_PADDING); widget = internal; } Item::Item (const Item& that) : Item (that.value, nullptr) { if (that.internal) { try {internal = that.internal->clone ();} catch (std::bad_alloc &ba) {throw ba;} widget = internal; } else { internal = nullptr; widget = that.widget; } } Item::~Item () { if (internal) deleteInternal (); } void Item::deleteInternal () { delete internal; internal = nullptr; widget = nullptr; } Item& Item::operator= (const Item& that) { value = that.value; if (internal) deleteInternal (); if (that.internal) { try {internal = that.internal->clone ();} catch (std::bad_alloc &ba) {throw ba;} widget = internal; } else { internal = nullptr; widget = that.widget; } return *this; } void Item::setValue (const double value) {this->value = value;} double Item::getValue () const {return this->value;} void Item::setWidget (BWidgets::Widget* widget) { if (internal) deleteInternal (); this->widget = widget; } void Item::setWidget (const std::string& text) { if (internal) deleteInternal (); try {internal = new BWidgets::Label (0, 0, BWIDGETS_DEFAULT_LABEL_WIDTH, BWIDGETS_DEFAULT_LABEL_HEIGHT, "label", text);} catch (std::bad_alloc &ba) {throw ba;} internal->getBorder ()->setPadding (BITEMS_DEFAULT_TEXT_PADDING); widget = internal; } void Item::cloneWidgetFrom (BWidgets::Widget* widget) { if (internal) deleteInternal (); try {internal = widget->clone ();} catch (std::bad_alloc &ba) {throw ba;} this->widget = internal; } BWidgets::Widget* Item::getWidget () const {return widget;} /*****************************************************************************/ ItemList::ItemList () : std::list () {} ItemList::ItemList (const Item& item) : std::list ({item}) {} ItemList::ItemList (const std::list& items) : std::list (items) {} ItemList::ItemList (BWidgets::Widget* widget) : std::list ({Item (1.0, widget)}) {} ItemList::ItemList (const std::initializer_list& widgets) : std::list () { for (BWidgets::Widget* w : widgets) push_back (Item (getNextValue (), w));; } ItemList::ItemList (const std::string& text) : std::list ({Item (1.0, text)}) {} ItemList::ItemList (const std::initializer_list& texts) : std::list () { for (std::string const& s : texts) push_back (Item (getNextValue (), s)); } void ItemList::push_back (const Item& item) {std::list::push_back (item);} void ItemList::push_back (BWidgets::Widget* widget) { double v = getNextValue (); std::list::push_back (Item (v, widget)); } void ItemList::push_back (const std::string& text) { double v = getNextValue (); std::list::push_back (Item (v, text)); // TODO try catch bad_alloc } Item* ItemList::getItem (const double value) { for (iterator it = begin (); it != end () ; ++it) { if ((*it).getValue () == value) return &(*it); } return nullptr; } double ItemList::getNextValue () const { double maxValue = UNSELECTED; for (Item const& i : *this) { if (i.getValue () > maxValue) maxValue = i.getValue (); } if (maxValue != UNSELECTED) return floor (maxValue) + 1; else return 1.0; } } BShapr-0.13/src/BWidgets/BItems.hpp000066400000000000000000000054271405711530000170130ustar00rootroot00000000000000/* BItems.hpp * Copyright (C) 2019 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BWIDGETS_BITEMS_HPP_ #define BWIDGETS_BITEMS_HPP_ #include #include #include "Widget.hpp" #include #ifndef UNSELECTED #define UNSELECTED -HUGE_VAL #define BITEMS_DEFAULT_TEXT_PADDING 4.0 #endif namespace BItems { class Item { public: Item (); Item (const double value, BWidgets::Widget* widget); Item (const double value, const std::string& text); Item (const Item& that); ~Item (); Item& operator= (const Item& that); void setValue (const double value); double getValue () const; void setWidget (BWidgets::Widget* widget); void setWidget (const std::string& text); void cloneWidgetFrom (BWidgets::Widget* widget); BWidgets::Widget* getWidget () const; protected: void deleteInternal (); double value; BWidgets::Widget* widget; BWidgets::Widget* internal; }; class ItemList : private std::list { public: ItemList (); ItemList (const Item& item); ItemList (const std::list& items); ItemList (BWidgets::Widget* widget); ItemList (const std::initializer_list& widgets); ItemList (const std::string& text); ItemList (const std::initializer_list& texts); using std::list::operator=; using std::list::begin; using std::list::end; using std::list::size; using std::list::empty; using std::list::front; using std::list::back; using std::list::pop_back; using std::list::pop_front; using std::list::erase; using std::list::iterator; void push_back (const Item& item); void push_back (BWidgets::Widget* widget); void push_back (const std::string& text); // TODO void push_front // TODO iterator insert Item* getItem (const double value); // TODO renumber protected: double getNextValue () const; }; } #endif /* BWIDGETS_BITEMS_HPP_ */ BShapr-0.13/src/BWidgets/BStyles.cpp000066400000000000000000000237331405711530000172100ustar00rootroot00000000000000/* BStyles.cpp * Copyright (C) 2018 Sven Jähnichen * * This program 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 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "BStyles.hpp" namespace BStyles { /***************************************************************************** * Class BStyles::Line *****************************************************************************/ Line::Line () : Line (BColors::invisible, 0.0) {} Line::Line (const BColors::Color& color, const double width) : lineColor (color), lineWidth (width) {} void Line::setColor (const BColors::Color& color) {lineColor = color;} BColors::Color* Line::getColor () {return &lineColor;} void Line::setWidth (const double width) {lineWidth = width;} double Line::getWidth () const {return lineWidth;} /* * End of class BWidgets::Line *****************************************************************************/ /***************************************************************************** * Class BStyles::Border *****************************************************************************/ Border::Border () : Border (noLine, 0.0, 0.0, 0.0) {} Border::Border (const Line& line) : Border (line, 0.0, 0.0, 0.0) {} Border::Border (const Line& line, const double margin, const double padding) : Border (line, margin, padding, 0.0) {} Border::Border (const Line& line, const double margin, const double padding, const double radius) : borderLine (line), borderMargin (margin), borderPadding (padding), borderRadius (radius) {} void Border::setLine (const Line& line) {borderLine = line;} Line* Border::getLine () {return &borderLine;} void Border::setMargin (const double margin) {borderMargin = margin;} double Border::getMargin () const {return borderMargin;} void Border::setPadding (const double padding) {borderPadding = padding;} double Border::getPadding () const {return borderPadding;} void Border::setRadius (const double radius) {borderRadius = radius;} double Border::getRadius () const {return borderRadius;} /* * End of class BWidgets::Border *****************************************************************************/ /***************************************************************************** * Class BStyles::Fill *****************************************************************************/ Fill::Fill () : fillColor (BColors::invisible), fillSurface (nullptr) {} Fill::Fill (const BColors::Color& color) : fillColor (color), fillSurface (nullptr) {} Fill::Fill (const std::string& filename) : fillColor (BColors::invisible), fillSurface (nullptr) { loadFillFromFile (filename); } Fill::Fill (const Fill& that) { fillColor = that.fillColor; if (that.fillSurface) fillSurface = cairo_image_surface_clone_from_image_surface (that.fillSurface); else fillSurface = nullptr; } Fill& Fill::operator= (const Fill& that) { if (this != &that) { fillColor = that.fillColor; if (that.fillSurface) fillSurface = cairo_image_surface_clone_from_image_surface (that.fillSurface); else fillSurface = nullptr; } return *this; } Fill::~Fill () { if (fillSurface && (cairo_surface_status (fillSurface) == CAIRO_STATUS_SUCCESS)) cairo_surface_destroy (fillSurface); } void Fill::setColor (const BColors::Color& color) {fillColor = color;} BColors::Color* Fill::getColor () {return &fillColor;} void Fill::loadFillFromFile (const std::string& filename) { if (fillSurface) cairo_surface_destroy (fillSurface); fillSurface = cairo_image_surface_create_from_png (filename.c_str()); } void Fill::loadFillFromCairoSurface (cairo_surface_t* surface) { if (fillSurface) cairo_surface_destroy (fillSurface); fillSurface = cairo_image_surface_clone_from_image_surface (surface); } cairo_surface_t* Fill::getCairoSurface () {return fillSurface;} /* * End of class BWidgets::Fill *****************************************************************************/ /***************************************************************************** * Class BStyles::Font *****************************************************************************/ Font::Font () : Font ("Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL, 12.0) {} Font::Font (const std::string& family, const cairo_font_slant_t slant, const cairo_font_weight_t weight, const double size, TextAlign align, TextVAlign valign, double lineSpacing) : fontFamily (family), fontSlant (slant), fontWeight (weight), fontSize (size), textAlign (align), textVAlign (valign), textLineSpacing (lineSpacing) {} void Font::setFontFamily (const std::string& family) {fontFamily = family;} std::string Font::getFontFamily () const {return fontFamily;} void Font::setFontSlant (const cairo_font_slant_t slant) {fontSlant = slant;} cairo_font_slant_t Font::getFontSlant () const {return fontSlant;} void Font::setFontWeight (const cairo_font_weight_t weight) {fontWeight = weight;} cairo_font_weight_t Font::getFontWeight () const {return fontWeight;} void Font::setTextAlign (const TextAlign align) {textAlign = align;} TextAlign Font::getTextAlign () const {return textAlign;} void Font::setTextVAlign (const TextVAlign valign) {textVAlign = valign;} TextVAlign Font::getTextVAlign () const {return textVAlign;} void Font::setLineSpacing (const double lineSpacing) {textLineSpacing = lineSpacing;} double Font::getLineSpacing () const {return textLineSpacing;} void Font::setFontSize (const double size) {fontSize = size;} double Font::getFontSize () const {return fontSize;} cairo_text_extents_t Font::getTextExtents (cairo_t* cr, const std::string& text) const { if (cr && (! cairo_status (cr))) { cairo_save (cr); cairo_text_extents_t ext; cairo_select_font_face (cr, fontFamily.c_str (), fontSlant, fontWeight); cairo_set_font_size (cr, fontSize); cairo_text_extents (cr, text.c_str(), &ext); cairo_restore (cr); return ext; } else { return {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; } } /* * End of class BWidgets::Font *****************************************************************************/ /***************************************************************************** * Class BStyles::StyleSet *****************************************************************************/ StyleSet::StyleSet () {} StyleSet::StyleSet (const std::string& name, const std::vector