pax_global_header00006660000000000000000000000064137227236370014526gustar00rootroot0000000000000052 comment=dadbedd3e0a2e53c41a59a30f4d252f0caa45c02 cecilia5-5.4.1/000077500000000000000000000000001372272363700132135ustar00rootroot00000000000000cecilia5-5.4.1/.gitignore000066400000000000000000000013431372272363700152040ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # OSX Desktop .DS_Store cecilia5-5.4.1/Cecilia5.py000077500000000000000000000074351372272363700152170ustar00rootroot00000000000000#! /usr/bin/env python3 # encoding: utf-8 """ Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import os, sys, random import wx from Resources.constants import * from Resources.Variables import loadBitmaps from Resources import audio, CeciliaMainFrame from Resources.splash import CeciliaSplashScreen import Resources.CeciliaLib as CeciliaLib class CeciliaApp(wx.App): def __init__(self, *args, **kwargs): wx.App.__init__(self, *args, **kwargs) def MacOpenFiles(self, filenames): if type(filenames) == list: filenames = filenames[0] if CeciliaLib.getVar("mainFrame") is not None: CeciliaLib.getVar("mainFrame").onOpen(filenames) def MacReopenApp(self): try: CeciliaLib.getVar("mainFrame").Raise() except: pass def onStart(): ceciliaMainFrame = CeciliaMainFrame.CeciliaMainFrame(None, -1) CeciliaLib.setVar("mainFrame", ceciliaMainFrame) app.SetTopWindow(ceciliaMainFrame) file = "" if len(sys.argv) > 1: file = sys.argv[1] if os.path.isfile(file): ceciliaMainFrame.onOpen(file) elif CeciliaLib.getVar("lastCeciliaFile") != '' and os.path.isfile(CeciliaLib.getVar("lastCeciliaFile")): ceciliaMainFrame.onOpen(CeciliaLib.getVar("lastCeciliaFile"), MODULES_PATH in CeciliaLib.getVar("lastCeciliaFile")) else: categories = [folder for folder in os.listdir(MODULES_PATH) if not folder.startswith(".")] category = random.choice(categories) files = [f for f in os.listdir(os.path.join(MODULES_PATH, category)) if f.endswith(FILE_EXTENSION)] file = random.choice(files) ceciliaMainFrame.onOpen(os.path.join(MODULES_PATH, category, file), True) if __name__ == '__main__': audioServer = audio.AudioServer() CeciliaLib.setVar("audioServer", audioServer) try: CeciliaLib.queryAudioMidiDrivers() except: pass app = CeciliaApp(redirect=False) loadBitmaps() wx.Log.SetLogLevel(0) if sys.version_info[0] < 3: wx.SetDefaultPyEncoding('utf-8') try: display = wx.Display() numDisp = display.GetCount() if CeciliaLib.getVar("DEBUG"): print('Numbers of displays:', numDisp) displays = [] displayOffset = [] displaySize = [] for i in range(numDisp): displays.append(wx.Display(i)) offset = displays[i].GetGeometry()[:2] size = displays[i].GetGeometry()[2:] if CeciliaLib.getVar("DEBUG"): print('display %d:' % i) print(' pos =', offset) print(' size =', size) print() displayOffset.append(offset) displaySize.append(size) except: numDisp = 1 displayOffset = [(0, 0)] displaySize = [(1024, 768)] CeciliaLib.setVar("numDisplays", numDisp) CeciliaLib.setVar("displayOffset", displayOffset) CeciliaLib.setVar("displaySize", displaySize) sp = CeciliaSplashScreen(None, img=CeciliaLib.ensureNFD(SPLASH_FILE_PATH), callback=onStart) app.MainLoop() cecilia5-5.4.1/README.rst000066400000000000000000000044021372272363700147020ustar00rootroot00000000000000======================================= Cecilia5 - the audio processing toolbox ======================================= .. image:: doc-en/source/images/Cecilia_splash.png :align: center Cecilia is an audio signal processing environment. Cecilia lets you create your own GUI (grapher, sliders, toggles, popup menus) using a simple syntax. Cecilia comes with many original builtin modules for sound effects and synthesis. Previously written in tcl/tk, Cecilia (version 4, named Cecilia4) was entirely rewritten in Python/wxPython and uses the Csound API for communicating between the interface and the audio engine. At this time, version 4.2 is the last release of this branch. Cecilia5 now uses the pyo audio engine created for the Python programming language. pyo allows a much more powerfull integration of the audio engine to the graphical interface. Since it's a standard python module, there is no need to use an API to communicate with the interface. Official web site ----------------- To download the latest version of Cecilia5 binary app, go to `the official web site! `_ User documentation ------------------ To consult the user online documentation, go to `ajaxsoundstudio.com `_. Requirements ------------ **If, for whatever reason, you can't use the binary app, here is what you need to install for running Cecilia5 from sources:** * `Python 3.6 `_ or `Python 3.7 `_ (preferred) or `Python 3.8 `_. The programming language used to code the application. * `WxPython 4.1.0 (Phoenix) `_ The toolkit used to create the graphical interface. (install with `pip install wxPython`) * `pyo 1.0.3 `_ The audio engine which gives his power to Cecilia. (install with `pip install pyo`) * `numpy `_ Array processing module for numbers. Used to accelerate the grapher display. Install the last stable version. Screenshot ---------- .. image:: doc-en/source/images/snapshot.png :width: 100% cecilia5-5.4.1/Resources/000077500000000000000000000000001372272363700151655ustar00rootroot00000000000000cecilia5-5.4.1/Resources/API_interface.py000066400000000000000000001014311372272363700201700ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ BaseModule_API = """ BaseModule_API Here are the explanations about the processing class under every cecilia5 module. # Declaration of the module's class Every module must contain a class named 'Module', where the audio processing will be developed. In order to work properly inside the environment, this class must inherit from the `BaseModule` class, defined inside the Cecilia source code. The BaseModule's internals create all links between the interface and the module's processing. A Cecilia module must be declared like this: ########################################################################### class Module(BaseModule): ''' Module's documentation ''' def __init__(self): BaseModule.__init__(self) ### Here comes the processing chain... ########################################################################### The module file will be executed in an environment where both `BaseModule` and `pyo` are already available. No need to import anything specific to define the audio process of the module. # Module's output The last object of the processing chain (ie the one producing the output sound) must be called 'self.out'. The audio server gets the sound from this variable and sends it to the Post-Processing plugins and from there, to the soundcard. Here is an example of a typical output variable, where 'self.snd' is the dry sound and 'self.dsp' is the processed sound. 'self.drywet' is a mixing slider and 'self.env' is the overall gain from a grapher's line: ########################################################################### self.out = Interp(self.snd, self.dsp, self.drywet, mul=self.env) ########################################################################### # Module's documentation The class should provide a __doc__ string giving relevant information about the processing implemented by the module. The user can show the documentation by selecting 'Help Menu' ---> 'Show Module Info'. Here is an example: ########################################################################### ''' "Convolution brickwall lowpass/highpass/bandpass/bandstop filter" Description Convolution filter with a user-defined length sinc kernel. This kind of filters are very CPU expensive but can give quite good stopband attenuation. Sliders # Cutoff Frequency : Cutoff frequency, in Hz, of the filter. # Bandwidth : Bandwith, in Hz, of the filter. Used only by bandpass and pnadstop filters. # Filter Order : Number of points of the filter kernel. A longer kernel means a sharper attenuation (and a higher CPU cost). This value is only available at initialization time. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of the filter (lowpass, highpass, bandpass, bandstop) # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time ''' ########################################################################### Public Attributes These are the attributes, defined in the BaseModule class, available to the user to help in the design of his custom modules. # self.sr : Cecilia's current sampling rate. # self.nchnls : Cecilia's current number of channels. # self.totalTime : Cecilia's current duration. # self.filepath : Path to the directory where is saved the current cecilia file. # self.number_of_voices : Number of voices from the cpoly widget. # self.polyphony_spread : List of transposition factors from the cpoly widget. # self.polyphony_scaling : Amplitude value according to polyphony number of voices. Public Methods These are the methods, defined in the BaseModule class, available to the user to help in the design of his custom modules. # self.addFilein(name) : Creates a SndTable object from the name of a cfilein widget. # self.addSampler(name, pitch, amp) : Creates a sampler/looper from the name of a csampler widget. # self.getSamplerDur(name) : Returns the duration of the sound used by the sampler `name`. # self.duplicate(seq, num) : Duplicates elements in a sequence according to the `num` parameter. # self.setGlobalSeed(x) : Sets the Server's global seed used by objects from the random family. Template This template, saved in a file with the extension '.c5', created a basic module where a sound can be load in a sampler for reading, with optional polyphonic playback. A graph envelope modulates the amplitude of the sound over the performance duration. ########################################################################### class Module(BaseModule): ''' Module's documentation ''' def __init__(self): BaseModule.__init__(self) ### get the sound from a sampler/looper self.snd = self.addSampler('snd') ### mix the channels and apply the envelope from the graph self.out = Mix(self.snd, voices=self.nchnls, mul=self.env) Interface = [ csampler(name='snd'), cgraph(name='env', label='Amplitude', func=[(0,1),(1,1)], col='blue1'), cpoly() ] ########################################################################### """ Interface_API = """ Interface_API The next functions describe every available widgets for building a Cecilia5 interface. """ def cfilein(name="filein", label="Audio", help=""): """ "Creates a popup menu to load a soundfile in a table" Initline cfilein(name='filein', label='Audio', help='') Description This interactive menu allows the user to import a soundfile into the processing module. When the user chooses a sound using the interface, Cecilia will scan the whole folder for soundfiles. A submenu containing all soundfiles present in the folder will allow a quicker access to them later on. More than one cfilein can be defined in a module. They will appear under the input label in the left side panel of the main window, in the order defined. In the processing class, use the BaseModule's method `addFilein` to retrieve the SndTable filled with the selected sound. >>> BaseModule.addFilein(name) For a cfilein created with name='mysound', the table is retrieved using a call like this one: >>> self.table = self.addFilein('mysound') Parameters # name : str A string passed to the parameter `name` of the BaseModule.addFilein method. This method returns a SndTable object containing Cecilia's number of channels filled with the selected sound in the interface. # label : str Label shown in the interface. # help : str Help string shown in the widget's popup tooltip. """ dic = {"type": "cfilein"} dic["name"] = name dic["label"] = label dic["help"] = help return dic def csampler(name="sampler", label="Audio", help=""): """ "Creates a popup menu to load a soundfile in a sampler" Initline csampler(name='sampler', label='Audio', help='') Description This menu allows the user to choose a soundfile for processing in the module. More than one csampler can be defined in a module. They will appear under the input label in the left side panel of the main window, in the order they have been defined. When the user chooses a sound using the interface, Cecilia will scan the whole folder for soundfiles. A submenu containing all soundfiles present in the folder will allow a quicker access to them later on. Loop points, pitch and amplitude parameters of the loaded soundfile can be controlled by the csampler window that drops when clicking the triangle just besides the name of the sound. A sampler returns an audio variable containing Cecilia's number of output channels regardless of the number of channels in the soundfile. A distribution algorithm is used to assign X number of channels to Y number of outputs. In the processing class, use the BaseModule's method `addSampler` to retrieve the audio variable containing all channels of the looped sound. >>> BaseModule.addSampler(name, pitch, amp) For a csampler created with name='mysound', the audio variable is retrieved using a call like this one: >>> self.snd = self.addSampler('mysound') Audio LFOs on pitch and amplitude of the looped sound can be passed directly to the addSampler method: >>> self.pitlf = Sine(freq=.1, mul=.25, add=1) >>> self.amplf = Sine(freq=.15, mul=.5, add=.5) >>> self.snd = self.addSampler('mysound', self.pitlf, self.amplf) Parameters # name : str A string passed to the parameter `name` of the BaseModule.addSampler method. This method returns a Mix object containing Cecilia's number of channels as audio streams from a Looper object controlled with the sampler window of the interface. # label : str Label shown in the interface. # help : str Help string shown in the sampler popup's tooltip. """ dic = {"type": "csampler"} dic["name"] = name dic["label"] = label dic["help"] = help return dic def cpoly(name="poly", label="Polyphony", min=1, max=10, init=1, help=""): """ "Creates two popup menus used as polyphony manager" Initline cpoly(name='poly', label='Polyphony', min=1, max=10, init=1, help='') Description cpoly is a widget conceived to help manage the voice polyphony of a module. cpoly comes with a popup menu that allows the user to choose how many instances (voices) of a process will be simultaneously playing. It also provides another popup to choose the type of polyphony (phasing, chorus, out-of-tune or one of the provided chords). cpoly has two values that are passed to the processing module: the number of voices and the voice spread. The number of voices can be collected using `self.number_of_voices`. `self.polyphony_spread` gives access to the transposition factors defined by the type of polyphony. If a csampler is used, you don't need to take care of polyphony, it's automatically handled inside the csampler. Without a csampler, user can retrieve polyphony popups values with these builtin reserved variables : # self.number_of_voices : int Number of layers of polyphony # self.polyphony_spread : list of floats Transposition factors as a list of floats # self.polyphony_scaling : float An amplitude factor based on the number of voices Notes The cpoly interface object and its associated variables can be used in the dsp module any way one sees fit. No more than one `cpoly` can be declared in a module. Parameters # name : str Name of the widget. # label : str Label shown in the interface. # min : int Minimum value for the number of layers slider. # max : int Maximum value for the number of layers slider. # init : int Initial value for the number of layers slider. # help : str Help string shown in the cpoly tooltip. """ dic = {"type": "cpoly"} dic["name"] = name dic["label"] = label dic["min"] = min dic["max"] = max dic["init"] = init dic["help"] = help return dic def cgraph(name="graph", label="Envelope", min=0.0, max=1.0, rel="lin", table=False, size=8192, unit="x", curved=False, func=[[0, 0.], [.01, 1], [.99, 1], [1, 0.]], col="red"): """ "Creates a graph only automated parameter or a shapeable envelope" Initline cgraph(name='graph', label='Envelope', min=0.0, max=1.0, rel='lin', table=False, size=8192, unit='x', curved=False, func=[(0, 0.), (.01, 1), (.99, 1), (1, 0.)], col='red') Description A graph line represents the evolution of a variable during a Cecilia performance. The value of the graph is passed to the module with a variable named `self.name`. The 'func' argument defines an initial break-point line shaped with time/value pairs (floats) as a list. Time values must be defined from 0 to 1 and are multiplied by the total_time of the process. When True, the 'table' argument writes the graph line in a PyoTableObject named with the variable `self.name`. The graph can then be used for any purpose in the module by recalling its variable. The `col` argument defines the color of the graph line using a color value. Parameters # name : str Name of the grapher line. # label : str Label shown in the grapher popup. # min : float Minimum value for the Y axis. # max : float Maximum value for the Y axis. # rel : str {'lin', 'log'} Y axis scaling. # table : boolean If True, a PyoTableObject will be created instead of a control variable. # size : int Size, in samples, of the PyoTableObject. # unit : str Unit symbol shown in the interface. # curved : boolean If True, a cosinus segments will be drawn between points. The curved mode can be switched by double-click on the curve in the grapher. Defaults to Flase # func : list of tuples Initial graph line in break-points (serie of time/value points). Times must be in increasing order between 0 and 1. # col : str Color of the widget. """ dic = {"type": "cgraph"} dic["name"] = name dic["label"] = label dic["min"] = min dic["max"] = max dic["rel"] = rel dic["table"] = table dic["unit"] = unit dic["curved"] = curved dic["size"] = size dic["func"] = [[x[0], x[1]] for x in func] dic["col"] = col return dic def cslider(name="slider", label="Pitch", min=20.0, max=20000.0, init=1000.0, rel="lin", res="float", gliss=0.025, unit="x", up=False, func=None, midictl=None, half=False, col="red", help=""): """ "Creates a slider, and its own graph line, for time-varying controls" Initline cslider(name='slider', label='Pitch', min=20.0, max=20000.0, init=1000.0, rel='lin', res='float', gliss=0.025, unit='x', up=False, func=None, midictl=None, half=False, col='red', help='') Description When created, the slider is stacked in the slider pane of the main Cecilia window in the order it is defined. The value of the slider is passed to the module with a variable named `self.name`. The `up` argument passes the value of the slider on mouse up if set to True or continuously if set to False. The `gliss` argument determines the duration of the portamento (in seconds) applied between values. The portamento is automatically set to 0 if `up` is True. The resolution of the slider can be set to int or float using the `res` argument. Slider color can be set using the `col` argument and a color value. However, sliders with `up` set to True are greyed out and the `col` argument is ignored. If `up` is set to True, the cslider will not create an audio rate signal, but will call a method named `widget_name` + '_up'. This method must be defined in the class `Module`. For a cslider with the name 'grains', the method should be declared like this: >>> def grains_up(self, value): Every time a slider is defined with `up` set to False, a corresponding graph line is automatically defined for the grapher in the Cecilia interface. The recording and playback of an automated slider is linked to its graph line. Parameters # name : str Name of the slider. # label : str Label shown in the slider label and the grapher popup. # min : float Minimum value of the slider. # max : float Maximum value of the slider. # init : float Slider's initial value. # rel : str {'lin', 'log'} Slider scaling. Defaults to 'lin'. # res : str {'int', 'float'} Slider resolution. Defaults to 'float' # gliss : float Portamento between values in seconds. Defaults to 0.025. # unit : str Unit symbol shown in the interface. # up : boolean Value passed on mouse up if True. Defaults to False. # func : list of tuples Initial automation in break-points format (serie of time/value points). Times must be in increasing order between 0 and 1. # midictl : int Automatically map a midi controller to this slider. Defaults to None. # half : boolean Determines if the slider is full-width or half-width. Set to True to get half-width slider. Defaults to False. # col : str Color of the widget. # help : str Help string shown in the cslider tooltip. """ dic = {"type": "cslider"} dic["name"] = name dic["label"] = label dic["min"] = min dic["max"] = max dic["init"] = init dic["rel"] = rel dic["res"] = res if func is None: dic["func"] = func else: dic["func"] = [[x[0], x[1]] for x in func] dic["gliss"] = gliss dic["unit"] = unit dic["up"] = up dic["midictl"] = midictl dic["half"] = half dic["col"] = col dic["help"] = help return dic def crange(name="range", label="Pitch", min=20.0, max=20000.0, init=[500.0, 2000.0], rel="log", res="float", gliss=0.025, unit="x", up=False, func=None, midictl=None, col="red", help=""): """ "Two-sided slider, with its own graph lines, for time-varying controls" Initline crange(name='range', label='Pitch', min=20.0, max=20000.0, init=[500.0, 2000.0], rel='log', res='float', gliss=0.025, unit='x', up=False, func=None, midictl=None, col='red', help='') Description This function creates a two-sided slider used to control a minimum and a maximum range at the sime time. When created, the range slider is stacked in the slider pane of the main Cecilia window in the order it is defined. The values of the range slider are passed to the module with a variable named `self.name`. The range minimum is collected using `self.name[0]` and the range maximum is collected using `self.name[1]`. The `up` argument passes the values of the range on mouse up if set to True or continuously if set to False. The `gliss` argument determines the duration of the portamento (in sec) applied on a new value. The resolution of the range slider can be set to 'int' or 'float' using the `res` argument. Slider color can be set using the `col` argument and a color value. However, sliders with `up` set to True are greyed out and the `col` argument is ignored. Every time a range slider is defined, two graph lines are automatically defined for the grapher in the Cecilia interface. One is linked to the minimum value of the range, the other one to the maximum value of the range. The recording and playback of an automated slider is linked to its graph line. Notes In order to quickly select the minimum value (and graph line), the user can click on the left side of the crange label, and on the right side of the label to select the maximum value (and graph line). Parameters # name : str Name of the range slider. # label : str Label shown in the crange label and the grapher popup. # min : float Minimum value of the range slider. # max : float Maximum value of the range slider. # init : list of float Range slider minimum and maximum initial values. # rel : str {'lin', 'log'} Range slider scaling. Defaults to 'lin'. # res : str {'int', 'float'} Range slider resolution. Defaults to 'float' # gliss : float Portamento between values in seconds. Defaults to 0.025. # unit : str Unit symbol shown in the interface. # up : boolean Value passed on mouse up if True. Defaults to False. # func : list of list of tuples Initial automation in break-points format (serie of time/value points). Times must be in increasing order between 0 and 1. The list must contain two lists of points, one for the minimum value and one for the maximum value. # midictl : list of int Automatically map two midi controllers to this range slider. Defaults to None. # col : str Color of the widget. # help : str Help string shown in the crange tooltip. """ dic = {"type": "crange"} dic["name"] = name dic["label"] = label dic["min"] = min dic["max"] = max dic["init"] = init dic["rel"] = rel dic["res"] = res if func is None: dic["func"] = [None, None] else: fmin = [[x[0], x[1]] for x in func[0]] fmax = [[x[0], x[1]] for x in func[1]] dic["func"] = [fmin, fmax] dic["gliss"] = gliss dic["unit"] = unit dic["up"] = up dic["midictl"] = midictl dic["half"] = False dic["col"] = col dic["help"] = help return dic def csplitter(name="splitter", label="Pitch", min=20.0, max=20000.0, init=[500.0, 2000.0, 5000.0], rel="log", res="float", gliss=0.025, unit="x", up=False, num_knobs=3, col="red", help=""): """ "Creates a multi-knobs slider used to split the spectrum in sub-regions" Initline csplitter(name='splitter', label='Pitch', min=20.0, max=20000.0, init=[500.0, 2000.0, 5000.0], rel='log', res='float', gliss=0.025, unit='x', up=False, num_knobs=3, col='red', help='') Description When created, the splitter is stacked in the slider pane of the main Cecilia window in the order it is defined. The values of the splitter slider are passed to the module with a variable named `self.name`. The knob values are collected using `self.name[0]` to `self.name[num-knobs-1]`. The `up` argument passes the values of the splitter on mouse up if set to True or continuously if set to False. The `gliss` argument determines the duration of the portamento (in seconds) applied between values. The resolution of the splitter slider can be set to int or float using the `res` argument. The slider color can be set using the `col` argument and a color value. However, sliders with `up` set to True are greyed out and the `col` argument is ignored. The csplitter is designed to be used with the FourBand() object in order to allow multi-band processing. Although the FourBand() parameters can be changed at audio rate, it is not recommended. This filter is CPU intensive and can have erratic behavior when boundaries are changed too quickly. Parameters # name : str Name of the splitter slider. # label : str Label shown in the csplitter label. # min : float Minimum value of the splitter slider. # max : float Maximum value of the splitter slider. # init : list of float Splitter knobs initial values. List must be of length `num_knobs`. Defaults to [500.0, 2000.0, 5000.0]. # rel : str {'lin', 'log'} Splitter slider scaling. Defaults to 'lin'. # res : str {'int', 'float'} Splitter slider resolution. Defaults to 'float' # gliss : float Portamento between values in seconds. Defaults to 0.025. # unit : str Unit symbol shown in the interface. # up : boolean Value passed on mouse up if True. Defaults to False. # num_knobs : int Number of junction knobs. Defaults to 3. # col : str Color of the widget. # help : str Help string shown in the csplitter tooltip. """ dic = {"type": "csplitter"} dic["name"] = name dic["label"] = label dic["min"] = min dic["max"] = max dic["init"] = [x for x in init] dic["rel"] = rel dic["res"] = res dic["gliss"] = gliss dic["unit"] = unit dic["up"] = up dic["num_knobs"] = num_knobs dic["midictl"] = None dic["half"] = False dic["col"] = col dic["help"] = help return dic def ctoggle(name="toggle", label="Start/Stop", init=True, rate="k", stack=False, col="red", help=""): """ "Creates a two-states button" Initline ctoggle(name='toggle', label='Start/Stop', init=True, rate='k', stack=False, col='red', help='') Description A toggle button is a two-states switch that can be used to start and stop processes. If `rate` argument is set to 'i', a built-in reserved variable is created at initialization time. The variable's name is constructed like this : >>> self.widget_name + '_value' If `name` is set to 'foo', the variable's name will be: >>> self.foo_value If `rate` argument is set to 'k', a module method using one argument must be defined with the name `name`. If `name` is set to 'foo', the function should be defined like this : >>> def foo(self, value): value is an integer (0 or 1). Parameters # name : str Name of the widget used to defined the function or the reserved variable. # label : str Label shown in the interface. # init : int Initial state of the toggle. # rate : str {'k', 'i'} Indicates if the toggle is handled at initialization time only ('i') with a reserved variable or with a function ('k') that can be called at any time during playback. # stack : boolean If True, the toggle will be added on the same row as the last toogle with stack=True and a label not empty. Defaults to False. # col : str Color of the widget. # help : str Help string shown in the toggle tooltip. """ dic = {"type": "ctoggle"} dic["name"] = name dic["label"] = label dic["init"] = init dic["rate"] = rate dic['stack'] = stack dic["col"] = col dic["help"] = help return dic def cpopup(name="popup", label="Chooser", value=["1", "2", "3", "4"], init="1", rate="k", col="red", help=""): """ "Creates a popup menu offering a limited set of choices" Initline cpopup(name='popup', label='Chooser', value=['1', '2', '3', '4'], init='1', rate='k', col='red', help='') Description A popup menu offers a limited set choices that are available to modify the state of the current module. If `rate` argument is set to 'i', two built-in reserved variables are created at initialization time. The variables' names are constructed like this : >>> self.widget_name + '_index' for the selected position in the popup. >>> self.widget_name + '_value' for the selected string in the popup. If `name` is set to 'foo', the variables names will be: >>> self.foo_index (this variable is an integer) >>> self.foo_value (this variable is a string) If `rate` argument is set to 'k', a module method using two arguments must be defined with the name `name`. If `name` is set to 'foo', the function should be defined like this : >>> def foo(self, index, value): >>> index -> int >>> value -> str Parameters # name : str Name of the widget. Used to defined the function or the reserved variables. # label : str Label shown in the interface. # value : list of strings An array of strings with which to initialize the popup. # init : int Initial state of the popup. # rate : str {'k', 'i'} Indicates if the popup is handled at initialization time only ('i') with reserved variables or with a function ('k') that can be called at any time during playback. # col : str Color of the widget. # help : str Help string shown in the popup tooltip. """ dic = {"type": "cpopup"} dic["name"] = name dic["label"] = label dic["value"] = [x for x in value] dic["init"] = init dic["rate"] = rate dic["col"] = col dic["help"] = help return dic def cbutton(name="button", label="Trigger", col="red", help=""): """ "Creates a button that can be used as an event trigger" Initline cbutton(name='button', label='Trigger', col='red', help='') Description A button has no state, it only sends a trigger when it is clicked. When the button is clicked, a function is called with the current state of the mouse (down or up) as argument. If `name` is set to 'foo', the function should be defined like this : >>> def foo(self, value): value is True on mouse pressed and False on mouse released. Parameters # name : str Name of the widget. Used to defined the function. # label : str Label shown in the interface. # col : str Color of the widget. # help : str Help string shown in the button tooltip. """ dic = {"type": "cbutton"} dic["name"] = name dic["label"] = label dic["col"] = col dic["help"] = help return dic def cgen(name="gen", label="Wave shape", init=[1, 0, .3, 0, .2, 0, .143, 0, .111], rate="k", popup=None, col="red", help=""): """ "Creates a list entry useful to generate list of arbitrary values" Initline cgen(name='gen', label='Wave shape', init=[1,0,.3,0,.2,0,.143,0,.111], rate='k', popup=None, col='red', help='') Description Widget that can be used to create a list of floating-point values. A left click on the widget will open a floating window to enter the desired values. Values can be separated by commas or by spaces. If `rate` argument is set to 'i', a built-in reserved variable is created at initialization time. The variable name is constructed like this : >>> self.widget_name + '_value' for retrieving a list of floats. If `name` is set to 'foo', the variable name will be: >>> self.foo_value (this variable is a list of floats) If `rate` argument is set to 'k', a module method using one argument must be defined with the name `name`. If `name` is set to 'foo', the function should be defined like this : >>> def foo(self, value): >>> value -> list of strings Parameters # name : str Name of the widget. Used to defined the function or the reserved variable. # label : str Label shown in the interface. # init : list of floats An array of number, separated with commas, with which to initialize the widget. # rate : str {'k', 'i'} Indicates if the widget is handled at initialization time only ('i') with a reserved variable or with a function ('k') that can be called at any time during playback. # popup : tuple (str, int) -> (popup's name, index) If a tuple is specified, and cgen is modified, the popup will be automatically set to the given index. # col : str Color of the widget. # help : str Help string shown in the widget's tooltip. """ dic = {"type": "cgen"} dic["name"] = name dic["label"] = label dic["init"] = [x for x in init] dic["rate"] = rate dic["popup"] = popup dic["col"] = col dic["help"] = help return dic cecilia5-5.4.1/Resources/COPYING.txt000066400000000000000000001045131372272363700170420ustar00rootroot00000000000000 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 . cecilia5-5.4.1/Resources/Cecilia5.icns000066400000000000000000004054141372272363700174710ustar00rootroot00000000000000icns il32NW ^uu]WWMpyqNgWVwywWgW5tyt5gWGxyxFgWGy FgW4xyxvxyx3gWty wZ?.&%&+8HbytgWWy \-(+D]cY=+,EyWgWwyK&,0eyQ+CywgWMyT&-,cy=wyy,--+nyuSSgyuyE,-,VyWkyx]pyk)--5wyq']ypmWNyQ*-,Ny@)jyMgWwyK*-+NwywG(.xywgWWy ^4++1IQH.)+FyVgWty xdM?637CTjytgW5xy x4gWFyEgWFxyxEgW3tyt4gWWwywVgWMpypMgW ]ut]WW ^vv]WWS{{TgW]^gW77gWKKgWK KgW76gW iRD>>?CMZrgW^ kCDH\otkTHJ[^gW\@JMvdHZgWTeALJtSTSg]|~@KL[oI{mvZGLKsDKLIWZV?LLO`xBLLRYarwCLLQYALLKpYDLLH~hfxvXJLKigy]|zDLLO>o{mWTcGLJbSEzSgW^GLHaYCJgW^ oMIHI\c[DEI]]gW tbWOLPZhzgW8 7gWKJgWKJgW67gW^]gWS{{SgW ]vu]WW ^ww]WW ]]gWhigW<<gWSRgWS RgW;:gW uVA889?M`gWi y@=A^zvT@B_igWd:BGl@\gW]o:DCTT]g]:CD]}Hmxa@DCACDBXYU9DDKd;DDQZeDDL:{mW]l@DBiW>\gWd@D@i`B`hgW hWMHN\qgW< ;gWRQgWRQgW:;gWihgW]\gW ]ww]Wl8mk DuuC ;;65srsr65;: DCututCC ;:64rqrq54:: CttC ih32 ʏ ֋ C]puwywup]C" =iwywi; /jyj2!cwywa!9tys9KvyvIKxyxKJxyxI9vyv7!sy o^RLIJNS^lxyscysP."$'(('&$&6dy b1xyS%%)+* 1ss3"ki";;NLPPNM;9" }mb][\_cm{ j`B9>ABC@==Js i2b:?EHGTdieVEHJIEi3 tzG>GKI]|^EKIhs ?x9BJLIhdDIc>rBBJLJ[QG^q"Q@JLLOx@V"Fn:ILLJoGOEdGCLJdz{9ILYsqsmuzd@KLLKfQWVTrYDLJnS~ŷPGLIsQ~KHLIuTSKXKHLItXlOHLIp`hWFLJhvMTqğbDLK]}VSPbzu?KLNydDHLFy=cbGgBKLLJZa<\F"OGLEs@Ei!qFILLJF~QAFwp?vBILLILXHKVr 3jHFJIE>GMJ< "FdzycF!ˏ ʏ ֋"NooM$ D~~B 36#vu#>=TQWWSR><# {leacgn{!vjB37:;<;:69K u5m68>@@TlrmY?@BA=t5 I7@CB`e=CBq C6;BDApm>s"~CADDBBV;?}C=ADDAK`6B@A O>CDAAkM8ACW 5uD?BB>8FOK86=AA? 3u|[C=>=<<==E]vv#}">=RPWVRP;=!uv"44 C~}B #LonL"ˏ h8mk @avva@9ݒ9pp~~EB{z{yEC~{po:8@?a`ut~~vta`@?97po}{BAzxzxCA{{on 8ې8>_t~~t_?it326^bf{}{{fb^^ 0CU^cfikmnpr onmkifc^UC0 ^ >Taejotxyzyz yxtojeaT>^ ?\eksxzzy zzxrkeZC% ^?Zfmvzzyzzvme\9^*OckuzzyzzukcM^/UeoxzyzxoeU0^(UfryqeU)^MdryrdU!^HcpypcG^6^lyxk^0^JetzyteJ^(\lyxk[(^@aryqa?^ IfwyzvfI ^TiyiS^\mzyzl\^]pzyzo\^]rzyzq]^]rzyzr]^]qzyzq]^]pzyzo\^ SmyzmS ^OiyiI^:eyf3^#bvyvb#^[ryz{{zwvupmmosvvx{zyrX ^Qly {{tdTA5+(# ! %)+5?M\kt{{zylK^0fy {weJ1# ""#$# "!  &1CYku{{zye0^^uy zyfD' "$%&&'(' &&%$#! &2@hzyt]^JkzyzvV/!#%&'())**)* )(('&%$!RzyzkH^cxy zxV( #%&()*+**)(+'()*+,++*)(&#Qzyxc ^Rqzy za- #&')*+,+*(,>TbnrodWC/&(*+,-,)&OzyzpU^)ey zs?"%()+,+)7Yszzy zzxb?''*+,-+'Nzye(^Yqyzc'!$'*+,)4]yzyzzg9%(+,,-+'KzyqU^*fy zS"%(*,,-,+*LvzyO&'*,-+(Fzyf)^VryG#&)+,-,*-^zy z[&'*,--+(?{yqV^&eyC $'*,,-,*0izyz\%'*,(9{ye ^NpyzF $(+,-,+/jzyzP$(,(6yzoN^bxyzO $(+,-,++ezy:$*,+)0xyxb^?kzyz_ $(+,-,)Wzyl&'++)*wyzk8^Zuyo%#'+,-,*Ay{H#)*))qy tZ^%ey6"'*,-+.pyk#'))'jy e%^GmzyzU!&*,-,)Rzyz8#()$ey zmC^[uys% $),,-+1uyz\!&(%Wzy uZ^ey{D$(+,-,)Wzyu'#%#Mzy e^7kzyn!!&*,-,-syz@ ".jzy zk8^OsyzE$(,-,*Bzyxgivzy qS^ `wyt$!&*,-,(azy w` ^#ezyzV$(+-+.uyf^7jzyz3&),-,*?{yajzyzj.^Hoyp "'+,-,)Uzy jQTUWXY[]N'<{yo?^VtyzW$(,-,(hyzS#),..-.,C{ytQeaxy{=&),-+-uyzR(/2/L{ywZfcy*!'*,-,*6zy zQ+5MLJGFDC>]zy_{fzyt!#'+-,)?{yzO,9x{zyzb{izyg$(,-,(L{yzN,9xyzdlzyz[%),-,(TzyzK,8xyzilyzM%*,-,'[zyzJ,9yzlny{D &*,-,(`y{I,Uiw{zyt]^0ey z{rcRA3.'(&&' &'((08GZjv{zyd3^Jlyzuqib`ZUSTV^ahqwzykO^\qyzyzq[^)avyvb"^@fye:^IiyiO^ SlzyzlS ^\pzyzoY ^]qzyzp]^]rzyzq]^]qzyzp]^\pzyzo\^\lzyzmY^RiyxhS ^ IfvyveN ^3cryqb9^"Xlxyxk["^ KetzytdO^0]kxzyzxj]3^HcoxyocD^RdryqdM^)UeqyqeU)^)VeoxzyzxoeV)^%NbktyzyzytjaM%^9ZeluyzyzyuleZ?^ %CZekrwzzy zzwrkeZC^ 8S`ejotwyzyz yxtoie^N7^ 0CU^cfhklnoprrqonljhfc^UC0 _cg|}}|gc_^bf{}{{fb^^ 0CV`gkpsvxz|}}|zxvspkg`VC0 ^ >Ucjrz zrjcT>^ ?]is ~si\C% ^?[jvvj]9^*PfrseN^/VizziV0^(Vk}}kV)^Mh}|hU!^Hf{{fG^6`tt_0^KjjK^(]tÇt\(^@d}Ç}d?^ IjŇkI ^TqˇpS^^vˇv]^_z͇z^^_}χ|_^_}ч|_^_|Ӈ|^^^zՇz]^ SvׇvS ^OpۇpJ^:j݇k3^#d݇e#^]}~{z|}Y ^Qu rbRE<:64 549<=FP]kytK^0jsYB512579:;<<=<<;:986459CTgyj0^`tS81269;=?@ABBCBBA@?><:8659CPu_^Jt d?137:=@BDEEFEFFG FEECB@><84atH^f e916:=ACEGGHGEDCBBDABCEFHIJ IIHGFD@CGJKLHC^|V^*ka14;@DGJKLLKKHE_ a=BFJKLLIDZj)^V}V/6=BEHKLKKGGo k=AFJKKIDT}V^&iQ07>CFJKLKGIx kCGKLKIEi{<@GHEBs8^[}65=CGKLJFWY;CGEC [^%iF3DIKLKIJk7@C@i [^iS2;^ j^7t|37@GJLKIFP58By s8^O~S2;DIKLKFXvw }T^ b67@GKLIDrɇ b ^#jd1;CIKLKHGʇ j^7rD5>FJLKFUowq.^Hz~39BHKLJDg y`dfgijjl_?Oy>^Vf1FJLKHFaDNTUUSO_\fg;8AGKLKGM `IRdca^]\[Wnb{k4:BHKLKFU`JTf{pu2FJLICl\IRvyS6@GKLICq[JVx{K7AHKLIBt ZJUn_VU]rz|F8AHKLHBv VHOPOLJIFQ}{}B9AHLHAw uKP[^]TMPNN|}C:AHLHBtqQTR\||C:AHLHBqbTWQ{zH9BHLIClvPWTwyxM8AHKLICg~MVVqwvT7AHKLICa}KVVtvs]6@GKLJDUxesvKUU~r{pl4?FKLJENY:DcNUXk{kw5>EJLKFEZ@Ee{JPRpgfg8=DILKGA{ YCJKYkl_GKNbbd`A;CILKHAl xYLKKGDFGPl\^VU8AHKLIBY {nhgkyR^Bzn6?FKLJEGʇ y>^0r:z _ ^T~C;DIKLKG?v1142U }O^=si7@GKLKIBT_08<:a s7^iC=DIKLJE@y;5>@>i i^[p8?FJKLKIBPa2>DHKLKHCEU3;DGEE i^ ^n9@EIKLJF?Vu28@GHEN[^8s]:AFJKLKIE>e ?5=EJIDZr?^eQ;BFJKLKHC=m M3;CHJHChd^OzKn T29AGKKGBxyM^$jLCFIKL KJGC>:IolB26BEGIJKIGDA>:77<@DC>;4359?BDFHIJJIHGECCQe^Gs{_G?ACEGHHIHHGFEDCBCCDEFGHHGGEDCI[psD^_ x`LA@ACDEFE FEEDCCGTgz_^0j scUJF@AA@AB AABBHO[kyi3^KtxrqjgeehoqxsO^\}|\^)d݇d"^@j݇j:^IpۇpO^ SuׇuT ^]zՇyZ ^^|Ӈ{^^_}ч|_^_|χ{_^^z͇y^^]uˇuZ^SpˇoS ^ IjŇjN ^3e}ć|d9^"YtÇs\"^ KjiO^0_ts_3^HfyzfD^Rh||hN^)Vk}|kV)^)VjzyiV)^%MerrdM%^8[ivvj[?^%C[is}}si\C^ 8Tciry yqi`O7^ 0CU_fjosvxz{}}{zwvrojf_UC0 _cg|}}|gc_^bf{}{{fb^^ 0CWcls{ zslcWC0 ^ >Ufq~ ~rfU>^?`po]C% ^?]qq_9^*PkjN^/WpqW0^(WrqW)^MooV!^HllG^6bb0^KrqL^(^œ^(^@iÜh?^ IsŜrI ^U|ʜ|T^_˜_^`͜`^bϜa^bќa^aӜ`^_՜^^ TלT ^P}ٜ{J^:rܜs3^#jݜi#^_[ ^QnXH<82/00//106:>?>=>??@@? >=<;9852.lI^lq8,/47:<>?@AA@?>=< ?;:;:<>?@ABA@?>=:5ll ^S ~>+159<>?ABA@=AVpr[C9<>@BCBA>9iU^)p T,/49ACCDC@;io(^[8,27;>ACA>J{N8<@BCDA;eW^)sl-/59=@BCDDCC@>e i8;?BCDDA<^r)^W_+06;>ACDCC@C{ x8:?BCCABA=Ej^?|-17<@CDCB=r8:@A==8^\407<@CDC?Y_5<@>= \^%qI.5;?CCDC@B49>>: p%^Go,3:>BCDC>nM5<=7 C^^508=ACDCAFx19<9s ]^pZ,6<@CDC=s8475f o^701:?CDCAAU03A 7^P[-5=ACDC>[ U^ e319?CDB=ɜ e ^#rp,5=ACDCACʜ q^7}E/8?BDC?W~ }.^H/3;@CDB=q jmortuwyg9Q?^Wr,6=ADA<l5>BDEEC[SeeR/8?BDC@A k=FKLKJJKJGe^fl;2:@CDC@L kAMhgda_^\Vzg{r14;ACDC?WiBQo{{,6=BDB=fgBQuw-7>BDBBDC?@b:>nFHJ}nfl56=BDC@; `@Mx^^W\2:@CDA;] {qovR^B}08?CDB>Eʜ >^0~86>BDC?:ʜ |.^rV3;@CDCA:g p^ f18?CDB=F[')? c ^UC5=ACDC@9/,.-] O^=u19?CDCA;Xj+353i 7^pC5=ACDC>:;08:8t o^]39>BCDCA:Sl,5<>9 ]^CU4;@CDC>9}619?>= C^%q;7=ACDC@@ q^ _|39>BCDC?9[.2:@A=M\^8d4:>BCDCA=7o @/7>BA=^~?^jU5;?BCDC@<8z R-5ACDCB?:5^ K-39>BCCB>TU^)rc6:=@CCDCB?;6B}w6.4:>ACDCA:62>]|{\:-059No(^UP8;>@ACB@=:7412:@ED=9/./258;?ABCBA>9uM^ k!S::=?@ABCCBB@><:8754355679;=?AABA@>?@A@@?><<;<<=>??@?><=G`}D^b hL=::<=>>?>>=> ==;< }z/+1$=:A?30hd.+{w51sq 63fa86TPfc{y~zhcTP75c`52 rm2/yv,)hd20@=:9/#,*{x =:ok %".,1..,&# om=;zx,+$#:8=<10ee+vw)1pn/ 2`b3 5QcyycO6ic08p jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 pV)4j\ *đIϐY1 Jj_C?1d6LS?j"W3 )trw(@=k̙fҸx 9|oY>׀ KޙmS8c΁Aj $3(WldLk=>~4u{v9PqL_!a\${iR")Ru,r~o۩^҃!n"ȲL&~q?ɽ2"  E c&j^;^gz#yCG)\*^ⱏG#*I{"I_z[ISFI}&ԟ*$(]M̾~f 7s}#nr\Kg&ygbCJ\Ip{,HGaE^=& c6Ͳ_5(9'LbskJ'{+w( v$ミ*vJ"?1%*CcEZMK߱h\J4_^ hVĺĹ{ߛ'?Sds'3X 5ڇV583Q8L]nO1]4͇p4X𘇃P"TT5[>Hm=A0vQg-@u!K/]0=儭#SgW{[Qb7{&zS-j}rL!s%u*iYV5҈.1:E|,|fȄf֐WleśwXM`K[mƜqfbS>t.t̖ɰowT)8[CN\ȋ mil"Jjq- iOސ:Ɏ<7¨CU=Xv3?:c% _QY[1J}B9o^.[( ,$R'D\No B;K3eAdQRr0 $ f1>v_maPm,|RW'<ڨr7Drn N waxjzҕwR6Zfб+ׂG>{u{ڲpAhIτ[d QZvǕ8eY/9Pߥ??kIaKHc;!wLeļN?`#rg)Bzm=n69SHxd/F >ʶԤ;.=(P\S 2&C&JblmqF8!jWfJmkn  )f#a-Xhye%FkŁ7}ZKwj+[Y[i2jCK+<]mu|3aD.%4?Yg+QoT \8āÿK{r@= R#4bYsx e./n4tvp%糀lA0iDzZ?8`w_ qĊ0"s0[̤3! fZ1 s8Ҵ8jdq_蝃y\,|r9$nU-TنۓS`9Xt^=hpfW8WxLϢhNjfZ dEȡpH#?{2)$Je0x(/W15lVVS^+co1$bc+x\co)ލgIFc3Br )jąؽd]GM~o8zcK- ZZKmq;{GZ$,5Z?naOx:ۯ:ޟ%wl+^a -m*@E:Yah2<8ÉUBG(;BUNgSBӒ`ʾ"4Fвcl6uc*S(+oC0y|8|Js#G ޝfM%ÆwOYzp,IyqD\ @Dkz)a7,('Z]18(B_L‰[ 6P=|.9M?LYnn 4+"Zsz0Ik9m4;4oWVEω6.y~q6p^߁"!XZ.3,CE+Db)~DtEvS ~_?KP+Yk8ǙE0j7=7ߪ46O(vUs!!'s`DHpcj"kҹh|&NN5uL.I\nooKD`fg:vm."HA*c^TӰl)k`4on!ǰڅ"PN|&Z|c$=!!6/[AviU7 Sv _ͦHCZb-WCϥ?ҥcD(s*A]P9J%ʖӎ,&}4l+@?쓶?S8WjnVؖ.sDy=_~O$e#UuҼW-H:Qdcb(x' $fmNSM&>6ۃ|odtS(֋.[ ##ɉ1|AUrRm BFdbʬgJlٚ0{Ur}/+}yQPW@,<A63ֈz&om|'2e&,˫?pji\d6( ^C, NR: ]Vm2IWk1WzpFzz h}2/mEchނӓI^j;5Qq 4'dX 0e]qe3 뫞<{㚦oMĨaćZ4 sZJ e&OV!rij`.q`s8afhM)tx8t0NޢF#td_֑LQT5蓐yh'2C$Y:B2w89›<|ݱm} t 8ĵv)^ؘa!I7"a Dt:xCQ+%tyzgN eϣ+FGHu:EXdhTr'gyqDT[ܲMoz1~C*!cVH}:$7'{\]r\SE /ƒ #9h QMurfSt?Y|'xrQd"l@r5#((az}N! ۂ[I-)jBqd;uL*ߩ|ԇ,+߹i[ s$<2"mAJB8jwlzZ\p:W7)nV9izВ|o[f)" ;iyFe5hV|qUV # _F3u-1Mpy-$db3C l Vԕ>s_4u-71)s 3lEɦSJm2We[Gplih @HGe_wXGXh5X*LG9x rx]!-q3\=,Hr1H(Жxk?<3)t(Ę܇Udtc~ϧFzk8r#cTK4ɣzC_6qea'9 :Kaİn8πLT-'9"N+F/օ=f Z~|׾ K+?~jUjgW_{P˰Xm\ dKgn?BO%7MkUYk,UMol,8F ZBـ{ E=qdڭ'\Od@c'AP"e|뮯_C"XRN5Cj4;{ T ,.^&:Zk:OfqlpxU_4pl2 !ճJc8*}N_sc;W2K8"igMQMu#J?CAP *Ep U^kRVyΛ=ޓHtlgǻ­끼%a˷+(Óp)ZfA/O99@ ʦ*?D)v1۱tڷDM=ܾx*|rj@>MɐKHR!.yy摉BAkK@VtD 5\U[2 +68QoENȡ=p-`H>6hFxH·F@8/; :蕇 F#CLI!YHk ccG*E*q]UV9/ ,E5W MQQ?748@,^|t<ɣtdhEAI5RvTU.i]95~Bcֿ1t'pLGݿ_˩r@seZqз|LlHB^b<'δ_2N #chak\vu;DK>dӓ='`5ty/,^j x]B䓪EpbS@A3CϼC.2m˼~7wl/b^6;}f ~ Y ڻNCŦw߹-k.Os7!Ag..hޤMX5SD#hmE=hQEdW#jm%n /Z{͔>PۑZՋxA -Y%&6ŋRo [ħCg04O jnO+xB2_Sy@d#eq&GKޏB9UcVK>K lvOH }'cϭjLեǬ̋T:q!ǥ8ڜD]iJ]g^R%2.{%ȵ> fi4/BsyckYcCLV2=wAӜ#~- ZHE$8g5Y%X%cHʼWBx-`d-i`p6$b Qe!apLn8̷Tu汮MOw-N_3<:0zՠnyק K"czhxm6EDn݈[__cljkdB8z]za5"3Ÿb0c u[C39Ġ9 4S Gɚc5)M$fPlb: kȔ p J$c'6L-mFQ섳NqOl[@7t<ցL jEH}h#6sBi2tbu* ӈU:iL?ἧ샬TCBQl{y\@B.ه\S3(4c픭lŠ:%}]PT jjI dӧDI#>(2z(1"ݸ",k*RQP:^:QoݽE!9b9MHTN^jkൊ!CM Zāͻ5nR-;^ y:9*p{*oc_a4!ڇfG7^}Tl{P9MO#Juο?l*C ; D{$$kK*#*fWNe :1 M:EJ*s5zW9m+ -;~$-o{o_5yx})ª.FIL҇W"FR}-w8)8_;?K+v16,4KU leqʼnQ}dL^xC1v2m (:dS`\<#wGZB0yDDDzYҾˇWa@7gt6pOmNwQqw8 ]i:h,AMHyU:\K \KM%vh[<6(( n9imneo"Cۧ@IjW!6.4O> +T+CJ#0YAMiJerUy@mzNڔZIrS-1?)X9@F>x>=2}V(3 9}Ɂ ImLaJ ʑ(JAu3xp$*X^h+9*}*gxݚ("Չ^Rz5h%NE.ZFy^2UZd˪}|^:Dt/Qh42 `Oo"Ehm%ϋ{/,|) zBl%_[7Rqm;2k~h>(/ȑ:;5[@?7Bd=UhL?>95tŶ/+6kXw %W ;VzyU? %ڦ*-ݩxydې/P_IrRӆ4y?Ep2Dla5iP0 5ߎg_die3^Ru6dt<}^+]ӪשYt 6(mf~D)c1@ Ƕ[U%~K\?SZG95{)X^w*Q}Σ0_@sg֒P LX?X m$IjϺޘ̟xn  RrA[:ypiJ{kWCUaQf{~A-4~Q_sW|4t(5$1Q`A;!y:aEn("bpRB?s ShĉJSs#:V'|XES2zĆtv@;J'!քjt0&d (fD6Χ.:Q'vY[ӊfmN>f( ;gTd6kЇ>*m˶8?#}D4c1w$N&fH_>sPG>V\)RnZQ 6 ;׮3#h Rai(TDڢD[qfmMJ8Ɯ2|~'kxTyֹX.x&q5t ^f03^nuk`%X'@l[ʌ=a1[J1W1RWch41[/ll]5'0]E)9uxǧ.W/Km1EJw;K,9((Kx2m.F_(9ug8bDEr3&pPC&@(mc-x|oG#4{0PHbEua)L5̞@*eh m5 ܪf-+*,NNB U}Uo5{(3S갏en&Z%c8]M:( 2*&c/t$\uNZ(E&a[YkF3IjePoV)OH:*Oķ^芃,!F@}z_ Yϱ>ib q9_mmx)b|\^3#KMz=4#̋1]Dnb?Ǩ=Ha=>8gd%3\f:vAн-"X%LDZ1sVQ*Z; [so/œWJ:1Y`0@clX2]Ы8W,L1rЂ1v@omioor2e$iMCy\Ea=Ə(Z!(( >lԻD;iNٝ٬:^V8zlQ+0$9a-ȕ WBLvڄw9p -ړS0t^Hak[UK[畼@Y.!q[Z(UfԎi܄vN_V|;gw Yb> W3푑B<]}{>7DBVɫ2TC İ[)3՞ <ǿwi5F`|Rs$sQKQ ehw(;лmr1*^$S!N[ooƴ٫ Zx-uI y)ll+0ƘLU@fY[ts^ߗ忆k{tjDo#wYMh]~en]%Bri{ >H!tjynpD|ӠeԦZK/rk葢@$8*xR+#\~` 0`p /lUPypbޘw9U}$#g!*3>nSi yKZ0]zx}٧v e _|W-ikXUź)=īC~}3J^XW8S[}=x1s7D ? MfP=BS-]&"1 ԅ |PkoŜ"PEgvMVc׊^%ZE8[T M[; ^4%l#ϹD!82KkJ*4=~]>/`ے!>ˮ#e6)@ohNȖf~#@)MT0c=mNg7 ?tbFڄlڳǽ?_p)::z7EL{t4/&^+_j_Pr0b_rHzb3Ȟn+*= QzvsyqZbF!WzC<'j fe$@WoI%~g?ڴɧ5h!L8dyS򪉧"q~fD0l9`UC^>NnұyY)4}% "qUkęSw?{DmF⹹ȔQTaƱ:C. scs|-@ِ L GX}Ե>z^Co=üS۽Œ@i:ɠp("K\OϳdVVԴ?fB\@C#X 2:KjkM9  GZsMWC\T\ /τWq=bbt` 9De$=rٜd2hD}tF"({RvKiM &9F`V#s !{4B|4L+z![,k7Cƀڄsinl7U({rz]ƿmle_IMjX̾/5EǙI_̠ȓi2 X̯/N#HZ߁:V̧3=V;tftJD',|ͅHh8B(@XYnY5@:2Z/ohBJ0a4*}&W=nu,_qd09OY)Sd-2k +yQ.&]DHXnXu35L}2.Y6[2lqŴ& OOOb:"T8G!U=9e&HEE~1H~ugOt13K=F0q$*^gI ZŹC4[j!y`k OS9Z -W TzQE uuM[5RkªGXMGN@޸L:?:'s 8gto\gp;T +SQ- iy}O/]"YwNΣ25N6)?1 }3^ƥ]l{yIrsP UyM|DZ5a -$XL$sNZ [t4cQSHGAs.3Bm1XdM5'f(n\_֠$ ?ͺ)^SvR<H-`O'!Xfa?,Va%ob= ɯaJxEM&mBӼ,M  j5o/݂HG рe* `yhG~z-5vVEp~Z#9[ƯBi2yw~4dN`[;) hՊ:yd]zoRu8d2X6Ŋݠ [n?_Y/kc5u]GuӀ}76Ж:n*,lxU=2c=noCƹֵ. q.5x! /W;j{k=b3jʩX>kֲ82嫔PkXNovH` _A(E Leazbm(;h y"Hm>]B*W`QJĔ`ȁI75#I'Jh+W+r"3Dԧ8e`,@ B# 1=ZNh6 Vq{O{bWe؅!TRl`_}򴡙H p8~Ӆ4}\'R8n)Ɉ'2tV6`zo ud7_+~5G:#$C%4DdLa`-'! _ /|8[d$A?$g)rx?Q ^\1J^]"\Vv|凴Ac,* Pw~*um)ёL+ث?4s隝 |ӷPG oR{!=\g%)ֲK8y0$6O(=M> [L[Xڤ3ǣ2?D-dǼYm%X2a*9/ס]:B a"3ӛҕ>gH|g'{jxGZzXjV |iW/#]{%2ـu,O̕b nka+2~DI6x*7힩{_dv#@vҝq88W IRj16ʨ>ջU pA#?u:JQ 87+9SԈtP|D_۞q$|f-Lu#k(+/t8V׋WJe].Bnpp΢uG䱃cm9h`)"CzÕe=가yAӋ[hL"@=w?7O0#lFJM@Y. hPpӽzD.\6/lG!a 0fLH_?`6UDBXBF?k4Je]uR[ZLRȺ . ց. rB>}Td\D P'lePƅ!awj\?$Gt4ﳘ#[@!pIYC`ha0[$%ۻti)ӴQ}I,[(_KcvC~~*EڞXPnkZ+C'h%ojӆ:Mn4gP_u.Nx5_r4GU ab!'8ǧbIъī x<@(7#eە!\r'fn=Zeׇ2\&ĶuS 5N9a>u)W|}zR+V`YěC 5ڲ)6xt&YHe'!9. D@<`rFG$ 'ilfgDƴ6ER2>Ldkq54kLJlCp"IZOb|sqqBVWM.pfG`uB8X&FZۄoO\S&yl(2yGr]G]7C O{ i0g}őNm|EJ@!P@KS/x iL1g$Fڜ$)NI# Ӏ&Fn%IzYRn74^&oS:|zK}vk8$L(JF1=p=7 =B}k;͢:F,Krs4]ԴF 'ɼ4}5=l=Lzij8&L/?@: Imy;\9Ɓ9`|rO0,8؝%@^b}ihkɟ>&{HurlD?3= JǸ"sA/P̻i:d_}TR 7H׫}>4Zh?)F@.T%#R]ĥ-431e9ډ^g >%4C;We5;WI -Šw? @NRSmx߹n Y7d]Qr4 >|E]Vef- ,yxaÁ"$ ,`@ٵ;Q,тJ',@kK.A>n6d*Hg8H_pA" ] 1(EBކY)w(_&gZ4F#,""6AsOY*"x}e~b* ]mIto@KQIhdOe%6==6+qc8[`'Ř# "MbҦIw+ ν;rW3}>`44D54A`;>Uf &B9~pyF'zlTTJq!<&¼u<89>;81;H ]\sDֹ ȯ̎#veC *6@. \iK#]`><YZKEc+MsLܾ6"}E)VE?eR|>&M˫FXG'Urmae0Q U}&v#m8RYn >=O503_&"hz &+󲒉FZuZDC a5XkYԋ{Q$?WBbO־gAT%˝4 Wld͸`ΰ{KAW"\ȎAA_M+Yt7OӏwN!e(C3cFOq1"0RPz9[KUq2Z~5 ֧L攮c')0s$xS76-Lf ,P( i)p [lcst+80׫+jXȌs1UK1Hv0bS:'۶$7%OՊ(Q:j[5qg'HMuB[ʜz=[x8zY_61SMf.̽/6mMmx ySր#u]~gÅ6U/Pp~5cZNa@Sf0LIm5cb ^ 8Zl:I,hLpVRG?i V PRcQOjݹg]nNnFa=xq7g35U57h[YEw~kZAI'1(B?h)jfI*;iSӰ\XdoIܻ+ڧૺf[ OxvVc3_<U}uI4lurt琊, cXƼ-NܺFjVgMUvԏfϐ9 je0Ӷo >Ҳs^)8I`0 ,y%e& K Ӓ@>##"-(]}2tZ6e`.5O ߔ FV,Rre/jPF襂o""nR#l|x$mrJqHYn}Ru֦ 6틓k@Rfn%tw2<l2<뺭b>鮘e)aǧp[;_kˆxijVi"Vԉũ6a.jȇ",Q ] Eh}n fʽt ѻJn]'pJfX6Ϛ)|ZTG~ { U H>J%D71HS1!(c |݉@GKyr-C'1QЖiF#{}?qňNQ h N4L3wC;$Di[&=,`# w}Op<Ŗ[u-97'\,5D- VNTis8"`5T$&¯]@ml-6Bk ᱢ@f,}aSC ^SrDlOsmpjG<.;|-)}i8IYWEzT+M~NN~NN`~NNNN?&txGQb#  oբ+3/e2hRNZ-V9FYp}+ h=\AuE"X xYT\.`o5#o( Ly![>@wxO)J:`HH@`6C[qQ{[c1j0}:7u=\JӆiYSzmhnaNXX춀Z_m˜Kx-:bT Y;y-\?V @B0@@Ki bgs hOlܼ8C(m`R]&/hYE\tpOWa?\XcS7 37.kN(&j4keE< Un+ScIdxաPmdK*|Joa8'Q#bPPg 'R=uqgMʇhdHo]L^l"h$Z.!!kt{;T~:RTR"qB8޾W|K&dؑ=O>hmR{`o^';8L9Wt2,~2rGr'˗kM$yؘx,AgPft=Y06~N"N#ہ)GFPDIhu(Bp٦c^wLѨ?E3(Gב4<".aA76 %KoX|daw&C4M0rշ_K?D^YA;d3KT(bAή5׼"=6#BM |#wK *:.1}.Ξ79U<#we-gnox(j|4Gg`Q.I-82Sl@U߶i<t KyG,"{.kd(ؖ4U=n ]ķnz0gs}Ly(qIpAp+ uY ]q}K8?f?ޕ앜hV-[~TX@VND3Uf +z Ng(AV 5/>Y}?IAI&O{ facU9H 0cÓ2oLZnhPi)/G vhRNKXo)̜:qC*n+/FL5ڔ ~_|Fɞs-V}ZSr>t+(rWT` sK0=gy|f2r5ϝ`sʁ5`KrϘkzjӯSFfz?`)00jb aGtN~kۨdhC47~fC= 3xP4P>#L6w#17e`6OןNKSlq…4ާW]c'isP>B;WPݛZB=@4X1P? d$7,[_#m^F6JRAh%WU0f lG`(6R^l|Zܒle9İՍfN0$Ϩ8%g8iBd?,EQ&QD{I9p~VI[|] )ZB =E 81풩nEϡȌJA@ݽT:"eH=5r\F}q [EsK 9u`7 lRr9EYח[U)yE Ya?{~={{?_ݡ."0JuXOv ZMޒN~ WXk(_YF((ݎG^J|kQ}t3k儢8(BerW\sX oBH sAtwoj\Z>9:+{nPb=؎@K CKш ?VA=OQ`M;ÑR_o}>Ư~ll3de.0 *ydi8{$A _NH6L3 l [wy:b^/U"βS7Mz5#xc]_ݷ0p,nFvlTIQi\4Sњ8R+;M9&0;7'^vւGBG5 rloT5vNU( j 98G8iTV6=܄s6FM P]a7$oN`SmxKt|f 돖(TU hjuq EZܥ4[ЌNWS\OhR m.Yvs1#-Xb#1*YS+ |ÏGVn1&v1RM_iy% Qt3obXr4PSUVHx%tdֈ+M+,zȮ<= )M{[Y&%kwbR.qD,}xizZW&f:-Lxw,A\2ܫ1lO`wO pӵ&.V܆!n;Su3Yq+ ꠉ!K9=Ri [x*%wԃUt)tDjpEkdMډxb)C=ۖYmX|(ZpӼxb]*Aӯw?'=Qx8N7.Gо}u8dT3b]$1Ǚ9džes/K[vۋ_6m>`lԾ֗BV6KICp?⦗ ,\x-G@Wcb8ĬXĀT]}3![ q))8>.>}jlo>\ 2ko%jsV8XA\,A۹ b[7^4-SVwv]?:#5ѧo9N*NtSch!7a'$iq')f( ʟ2aGjq*We{P77Gɣi /Y6kȣiR,A}t:sRM|D0-ӼtPQ@Øth4vvLi94tu\ :5:W~[B}.a3'KQ=W ;֊?qͷ;.`}"ثW_r *q3:hر4ڿYel+ֲ$fyMg32h[c)4T=a93E'v[N59&ɚ9n᩷Yk: ‹'N`;L/b9W7C6ibILXMͷUc_qZA 7jwa'00͡aM4`-8O-kWȰIHRDy~^ҝHg}t⒔FAc{wtUȈѦ.nޖʕ ʍ_|yJ<js%3v4◷3ȉ'b ӥLn5]b7 k|EѪN*WSk/;I. afݐ=PN9G)rx?()7Jxf)3aqe?g_5CcR2ҠQYeKݥ/qaD@# HIK-17"f$[ec!) x딦 g߃? Te4o. 㓛iGV<7hKp 9v+Y6af|QӇ GMuA#*E"`xK4+Cܫ3i~lKh Ha@h=rM6`'ppay`.Txcm#xoO@L5+|zf? ^Sx 8~ɭ4lnMO!C|(O-tf Ƃ%L- PVʍg qHd26s&(z UF7]E߈C렓Ӵ{iPjXyD13MH_,/iĐ-2o3iRB~n8UQ :v;rOߗ?D f-.4/!t䟁 et ֬1;<|[K@&DI7tvMkSO?Q;rQa#:[ܬ&R^wGce4?)-P~=>8>Ȏ~= &:QɁ~2[d[Ԇ!𷺳 h=<IRQ2tu:xX:_q[nZ=uX!n6gTOBUU0-ox'.9`IAbr|ջ!'n& H&NE Lwu?/v\𪈈ŠTxܱ mc಺˙tDO5dNlՂU8,)$**@_r]qG &:-cbipyʇ(ӥ{IltGuvƓY2]P0b f⩞Fmd}V(ȻT\/d; Mb!u8-ݴpi#ӆqŧ+J~ R2BpSpLŔE^D*(bc_רz}dl1F$MZƛ\A酲FOBuruwy(E)QVf4qqRvrmCS 1xBsvy"SbY3ݬciw&̬ #F+3F̮ONv5h ?7*$H''z;ZGi˘F&ic09 jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 dƧO]ZOnkL1Ӵ{;zv& ˋv^DI|ԾE>x`mV;(y5p6.6OcBӕp> lptsܓf)J"޳jBwT>FlZ5r,吝w3f-2ELRLMΦFs\fը(+`sػWܥ&ap$Y%$ ;> .g&_7x j:XLo _q n9f ݝLأWM6(`h֖p*Y [ cru;|GU]ݽXD`U1ppEPB,K\nL-{#LZ]')"PJ`O *"z6ო05"H"˿+G!qEx/o; 0M(! dl}$. 8 ;N4ЩNg'2Z4 6 tཎIb wroJߨ JMM' Md(_4)pɕ4$EMߖ~^`f1<+)]*:3U(av83I2X!XƆ',–àe_HE@i)]ʝ 8i@0ؾv{3mO.Z9`G^ ^|YАYÖBxQWˇqQiLAy5t&B8 nW,WMV z]1WӚk~ ^U'* r* X>m1UiF{15M$*zNX+dY|A'6ky?GY)&a IQ4SrumCR㷄g{sC!$k+`:= ﶐+Z)xvB+_qt<ךf!1߉27kn,@ḢQ:0R:Y;=;QM[Fe鈗o0m6iJF^Oيl" ՀtJlQW(֦E1 BkE(R>,_hES(1` ,@?lDĚ${Ԯ1]ĿK?i`!AHw7%-Fej.hx̬Y!Z' LX.p0 %q{͏"u:/,X&o3 Sz XiG=D.)!kΪ OTA>{u{ڲpA߯ɽ'^fw4Q}鳅@Dx-9bO͇SçL?[tmiG#=ȣ Q]eX߸ڏd8N8p*8 ,߾'O#`"7(y?Yq9KQ' HaDDWyń C\ɎԒ+ΰʼN\ՉU2Pq9kwvIUs DlFmypo_2oLJ~ F :S$ky+ve5a؜.,|r9$nUAJ8/z&F%rٖ[d<?3T`qě`|7`n4Wgy!Wṑnǹ #)2.ěM>yE.,as1 ٶ$6Zd)2x.8;W#0=M+ȶ|3au=)OŸJ?HBOOߡ4%jR"lg)os: Gt!KYaז媆ſ0exS{x[3A 'xgVxfb'L)W9z/EhٍuJ.BeeXPɸ(Yk`'*~!A"ȁZkDnΩŠaT ;؈o?-X o*(BcjQe9NoJ{~`SaTY%JIohDſ.Rw t#yM.P7`dP %~>Yvz Nk zP!<"p#U:\Y]up꼲Zr:K"0 `1[I2_.Sp92RI 0S^={H F*rݡH}I\QB݃kk'x""2'{cSZN%I'lmP+v\ GA%59~$ddɼæ ѹZ|ΛA;^@$ic9 `ػ ׎8R뗭q"?,_Oǖܼtg~4|!LԚ\VOGL?$<?HL*埇Cqz{H]a% 8*LjCm >:s-.`I'A2 ߮uد/_;VwVq1RS>!Ӑ 跊홼7oT(_CcsM= Nl:̿7VjG_ْcT/ISGrVm&EaG{Ŝt4;zB^ vmMJDeЅR,Fӑe.5eBր?B H;IxW$tG3?OMQ>,M%7Ր%>>-…']2PGV -%.=y9!^Vo|aR;ZI4!R"΃RU`.`D do%6;)r1L\6QKnʀ$ yē)!V?A F㈴֙c'oFvͱ߭Gk>MɐKu|A88f~ܕR)k}h1&IgEg sEp?:A~D ~\& ÷|߱M܇w69GM^p=>e?ՔҕU17Lțo̵Q?&%P5iЦ}ubM8s\5ItሴMZ6ŹIɣdv6Q-;bMB)޻eVb-VHR4gsOqy:0++# r2em dZvaA_L_'8d-1PHk$gToT(Ne˲-3,F?/+Y*|~F9{j|N*Tpc #\,ƙ\!83K&r3Cs\\l=oRjM'F.e#t^jZ.6A<ҕx|8 IwT{ Ήuy>j'j//1[H| 61MRŅs^6`[%>56Y] VwF⬡HE:x1^ 8z[D 2^mt9 D'硐4`y ֭)`Y|]"zC\BP|;gx 'bM?@ 6_JOŕ$ m %疐vp0C"8wƖhc*Vi`$L)N5 /cϭJ`e|کij'湄 pn}bn+opu6>Y<&6{ 6NQrgR?p 3HH6!rJ|QN2r N 7SofiGom4ӟT+NEY6WtƟRY.tFgDoo+PB&oQ>paO[Xce5HLBG,JSUOjIdm.# `])ZBE vd{ {pob+xo*7n6.DMZ6fQ_^a$[kHЪȣ{7 /d˾*;Mחo47t<ցL 8?]DvKYc2@2 V`^5wD @*D7A(U?'~~t~Df\=ݝi Ž1ᬥQձ;|x,Ӵ~ )}tsD6xMQ:j T.8>Mm4tw6˔Ѻm9o.ҳX*e:ti盰=94؂^%~kJ6@})-)ΎgB6d\g,8x _j|l-HHN(YQa]&ݫDɄPoWV(bC5VrTTX !;1́_&Eeg\vw5B*, TG(fCVQh*7򄫃W9rя!yѿsz V*:.4 ѿDN'!nW_5rIeLF렕W9O]CUO" A ~UpoWUV$KC,u]Zynw˟Zԃ_Ci1<ّ5j vOSLj(Mn灹z|e-&k]Gs;7}z}1x~tx( fd$lQܘGEzy= Q7Q3sy莗lnĴ|[e=ـtO^ӪNDD̽'#%מEbIa r#? oS, hz(pWΐp_¿SHuYP.>6ZKY+qYSN0'/tyzKmgAALO*U ѝT,,++Qv8d^S6"Qz|~`.b}jJɖP'/ɱ"Rf8en:-f##kȥ.ќe^xrL$KZKAr?һ*D}.BPoVRQ{s^HUV`ET7q H[Y!?2.cB/@q3|e#w@8v?٘[x}aS|S&,Z䐹̜uڻ-pXˉ8tJe<| 4-8G5.NyΕ.ŎysgU^r4܂կiVQjrwId,ZްEdICƟDRȝ7Ns7qUR?C6IZvzy?9iXTr߫C6_|V\qƓZ `E :-QAߵUݸt>{ۅMN1@*@PKx[gW_'pt`˾-W*jA`MKba(F)Q9F9lv'208PQˇt3:4%\u Q?۾i'fqd&(cb6=h+Q"0&xE~$@Vo"R,4]9JR,x-32~{GպyU//bGn+B;,d갂~!lY1L\dKa'C vqd9n'2L: K-s/6r mkmݛזxbV&4Wo:| ;^XAqۖ1f[QX7zqRwhv(g$^YW dzcӲ A"p{y/KUCKivoEVя,/XXl%>Lލe.z>#`>( Zc r qގ,މMkOI,Z&뫅t*?ڽ(ViCkBXztH,OUpt"JxG -cU|R Y 59[ƯӕlEǩ+l˫25^%:+XJ#z=ZЧFY?C|H߬ڼMe63+U,ەj'ڢs4MC O 7 %43jLȀQCH* l@nTDa\i{e 4Ȇ{Xkf$ Y?Sw מd؎'(tA|؂=C \[ꑷzk$uF6d>R5(A-Ϥ 6؃=?gb)eK)Uuʞ\F*+ēb[U'8Q)Q!Vl#! M 2_ 8}}\1Eĩ e%C%rֆC[O4V]?Ho#Q]{X @ 9i)c7@gs>SY,シSsӨ6Ց(}\<͵ǐw̡m#<‡({@Bs헖xCDeTv?j|:g4 Ƈ`6~D+Z7L)xdļ}W_S<94"jc<.Ge ɺbGI 8lO=BL\6jw1gXN2):szՠ)24XBNu2F4f8+- "*̓1ϸywR"]H&Ԁ6l[>=2.Zq΃N]*[3}Sf{|Ā;y;ޠ3v66[ms`#Vsc8ZY'CRK)Q:n|\ ɓVҘ>>q~Jvh9js0ODVm&N!caPYt8А.R90ךHUUFr*7vW 1~??@S\S7 hTWv9F C6/aHZ&yD 1AKYr ƌf T1 ?OL-Iei.`X9;RO |PD4 &6>U!,W-6V[>5g#<5w,.6I3VԪ2oꐞƑ3Amn &w\g߅".sv,;eS){tJs[sYuYtgv K* EYs@~&!W#uhY@v d`5JwCVh?2k-.1[:F n7F ,'Dng."9`7TQErەHT*) z.mZ\ѱ$ )Qz+2y8G$kP׎ؔnMvqQ,oE&fן)?DFS`i<6{kv9 lhm5 _.TFj{EYdԠWE+APڑ0 WlvU~s%,04ޭIɡ$naL8J!lP(q_$VU47tۢUT>P)I[[2i"UD"KPڔ:K9i*? ]m ,HF@|9|O z]ypd^}@lH:X\rNh.yVf}km1(o|)?x8AOژrjr3YId@ُPg(TQxheG Ӭ$eITc 4/c9dzݐ@8˔ ;. Z 0 $IExrhD5K@Y(f*̏WiEŧ Կl"ָU,Y`A\ :/-ssy}v#f+WbJ=JA%h C Ah8_Qr_-f%2,;8z̪\l,Zmr!FA>as<iK֪5mZ>bR 7|KM6foW(NJ~ş«<+[W,ϡ9à0>QQe+*lJoid1V1T1܈Ql$黫SInd&x{r ;lZ`y2aP)I%7gEQI]%b[)>7 ҮɳE1 W%ȑ9 45`h߲tĨo&"F`RT M6l 52`Fd-}[&J?2odU"/bgjSfܢƔy'.#*}"$ESR!TQOc|Q܃ꐘQ2,9Udc!0BdFrx[6ŞK:=ūAp6`_7!`^ cٌXO\'M,գGvMO|fzGV5GUdE|bU}-+R=Y,šg7 Ouc)ju¸XymdM:UIٝӸgJً b@`3BE/ GS=_ ,<̜+$#cJ?5K5MkE]CC7W3E4hƥk9fLxZwsr,βS VT2bz)Nf>'泌 VJ5f2Eogz p d/QPf=MiV%]&n=GǺ .GX\]1tkϰGnKt2tT2QY1f_"w vi7d:eѺPl:*3өythvSn5#=} 6&hjqRjZ! "㔿t;} |gf>nP%^Fa)3kxx<{ GLCv4QqxL,ǤlXX;?k:`ty@3b]B ᔆ;t|XNYj Y~[\.(!>PaŏpW/OeH':Oz,-&/SboVMfCyWGI'$xG _䐨}-w9vГ*96p USH r7슭Aa/0=9K\:[qɪ7GnX2,ޞAu Zl>KmKOaq6`V$xDUdUHZt %Ot3!Y_TObF"{XyAi W:KҦCs>L&tKh@>Y0N*|5P$*d `xA[(jƒ&kx, yC2}hZE]!n0:%I؁ٶry%h| 1'1Š JV&aT)AOOn'a?Eryjt Q=86F N<\E[#MU\7vtm@&VZN.G .1NLNJlP~YHT3:[D9 􎋹{`DUo2,F#!4ɗR{/9+Dԝ{;Ϥ V4*Wj_)g<-:CT+' fwC4KWPw\r'Sn.A昶VB9БeGZ mw=bC++{]N܋*%:1`3%;]uݏ-uY6޾Xgt޺|1"o bV`ddop6H%XIs}c!2rFmw>|7MȁeVI!cΩIY3Vؘlʯs:+h +`ז!er<UKH"0`]$%̻{;QD_확W3!B{H[L%0wqn 03;} 'dbƙok86Xه:|>(`:)⯕;\n2#tdD+Js~g'jL $B=(hדL{lOܺA-+v;2\B̿_ PCg4626+ l G,3)s;/2)4$)]bKzE@șk1rnʚKd5%{fך[Z]%.DPrP~ V^Hs8r?!!.F> @oɢ~:f4!تagb 䫳ƹMz.&MRޭϢ+f<7nh< E |KnwDH1IW0Zd$ˮ,hO+\);]5AB&uh,k\Vg)K%vk@ ^8:{1̿P8Љ}gdv=<>9h LG HP&m0Ԝz痕0"&G!vj)2.aLgm聺Y s0[rPß"۝G$HYC]g-'GND.\6/_?Es˙ݶj*cJ}Ѵqa?w 3¦[>Lu@h+kZR](dRm@!A8186 (yGWbǵ0HQZFqX83jcL DP8gW&H5 ?Mᘵ |! h@Wiol?/(_@H+B·(ᄟP;o%).lt^I I[Ӑ^+GY/;iXj @#fkzͫ|iX޵Mmakff[j)3(}5f)mV鎬x BX U&2ub;K}hh/f> {5";IՊ*LMV& Ia>7jUri5pvqf2$޿e&N*xz3tW4֖jrN{#kZ1d-䒉Q$I$I$I$I$ΜBm9f<)fch(FΨRl/ 1s R^iD5'30*$P}ʳY"Ş1-3_Qmy{ ת=ӂaP,U:x Rݟ]r3m#@nl{zo2nLSuGEx @li0E=cRU1eW$R$,G>:"G$]r;|b"@&0 <Ӂ39b:Fpiv~>OH\1@xu.G ZyT&#O 8ܟ}vP g6P쩾cyf))nr# )vW4􇾹(;وl*Ab! >]kE_ LL cIb,H657^M }At~pXu콇{xl8Ӱ(1mBԚLxJ[-s1UKpl `P S/WkqK>훘piZh3*_[u(;.t&:Dw6H=U|su^!80"Ȓh *,.8(ƹ4t+OYKyV|k؊%i;]vG:ȍ>%i`Qx4.?<g:LXSC:1PNbw]r]kWc4Ձ L,Ui׫+SKw0iտe|)m s(٘{MF45+S@B4F*5Е2tͯlҚ<- /SXq+ƨqèpN JrYCdICDVԂOv(ZlRxl#wTN+r?-sRcX?Xwr¯Y:#>];^A+micc.ZܵT-Pm+X-^fkܰ"*SxpɼTr~?Ѯ ~-Xsx n|3J:v;{Xߋv((@;ZEZPR D4xIA^"8O.4o*ٝXZ VCs9cĨ mTvI6<]# CؙJ.>#FV l}aPDSU1vJq0$$ѓ9n<׈^ufݐ"8aJ;խ1H?hé PtZ*Q_ X:S {R<5uUcns}dHAߥ._&՝8giH݁Cc$ynY.dgFPKLV>C}Ɔ4q${z[gABblpkc`ߘTk)|Pstov:҈AgJ}6Ktt4*z(˔`ю|<[n9>POQMO#a R^3H BcU'O϶Q3C/)r'?ţ7=mŹ8ɬ&P,[a8/j[kq(wC,>S(f"zAxk5?p3=ʃ<l{2>At9CRH79PE I}dʘvg e j\8mA7[D1)rv l!E I8t@Բ_9r*-j 4͚eR@~ٙ1t(|O~T0 0L#$Qv'΋E `i BV (п-pg9>:eIنXqO:kG֐~j&.* p!XEi\ ^ONֈIGw/h盂 h:]VSo2xlSyQ|lvop.0T<ޓ.1:)F#ސi4"\֊.X&^w}1޲RPlrDL (PnyR8 lY)0XE 7 nzwZ K}$#k hOPCؾV/o 9SXN/ ?!ōQaʍS:zYp@<ĕaN3v%Y‚ZX/#n 4 |]`q9ͦ՞zpe_ ~Y K7s3/e]Fokt΋Dua@HSWWpd){вD5mM+@K@ͦqrsܪYLvBu(c G-tEЎRJziT2 moadRHɉp8V5;V&t5P<ҩԹ0nl^'qzKJOt{@`Ӝo0ZjRq x6ːoi<!q!~mRBxkDBV2=sy ݞV"K'ى|i;jS ufW9qlfuMYfo!^0%- ^[Tk$Vkֻ4*۔ -oe>XJg\/L\S r.Cvrn: zU(rV>YSuk4;PR9zǽazEyR) 2ܝC B}WN^}Tu)VॴfDv+}c0 5݂=SYd/%hPDnN@'Z_WݑL 'ԱMI_6HAb~tp_w1Hn۝.xqAȊ+h<Ƨ$3 'Nזˌ8+@%Wef(8}-,DiDgg_]"[TQTjtpNb+S)Y Ń9SQ\٨ߠu8~(}7%xү"*Y$ݗ4YNqkwСkF_z2&`u'dޅ~pђR}^Uf b5Ь<1Pk<=em\7sGonyk*(G?-O&4%rIHX2]M[U;q\=GM'`zdw2e5b- ~Hxߪm X zzĕɀ,l|qé՛ WHp%:93-|0A7b3\LH5 oG%LJI_M.PiV:VkH&?7"3ɉ7UE*0}%^UdSͧ,ݭ˹Mԣd$/5@ R+YzFRui=US^ R[ yPDg}!JbvĞ0 0$V@ 7&Q!U~/tI͇z*IQ]C3—/A`醆 "?4EλeysӔl]bb~ДJZ "_Z)ɚz #|:NFiOp;=AT>m;搄qW`̸Cn  %]$ cN-KBj) ۽Lk|TC*39p*gYEe@"Ab}v7k]tY ~Y¶MM KihLeWr/k{o>={V}ӭZpxA#2ԹA}+Ms%EcV5AgO *C49DfT@ lnaGPp H?Zֽ@eGݹ edS1,R3csax{lTxNJR~K{pE;g5حGJ+I{bs,l )Qu,+,YPu3*hW\=4?%f ]XY"/' )Gh`[yl~=u+\; u gtK){EB!Nbe|>R=L[+6噋fokb"-h)IN # Dr"&D?"b~Pg!!Yu8<'mQ Zs'xh_Bqu"b&t-89p5EWnj3# vwzJLuǬW7Dz}  ~Nk晭\܃.W#hy\OKVlmu) ; grF'Bb9=kB+Y97H'.ѧ-/ҜI!k1 Yn5eU"ii 5)#Մ-;6IT1/5R^Շܼ &j`OZDցMsޞQs5M#tr*&IQ4|[?{fS"D38T? ڥD Yw2;^`>R/gOs @rWzo@*m/ҽ%)ց͓BnQ!]̣b]]-Nj'`K JT޶2Nk] J4\Sij8~M!৯^^h6e _K4 M.]bi6"My16z2Ưsw,*KYK 9d:]qmo=SVxIUht3֌un[d9;Ϳ @:JQJp*mѰpїz#4!Ro~|a (*w_}|pvљ#Qz|v[' '-[#)~rH@pF`=8m!j{iׁA ?HDWjG+d}绐&R^7kK$AA>6Xm$rcJ Fۘvz S-><tq#DPD 1<8*l>WDpfΤjl㕲Tц )I =~_=n}V`P~uФ5C(W6Xm{^F!Ks8zѦX]M}x$KsDH0 1'٪\hUNU-:"cbwg'ל؛W҅Ҋz)@1R^wE0͵ 8{oU4{|X%~v_|WUUQ]XUvWN[l4R<XcJNdel;=֓m]L[ŌN]Z&SJ;aeUkP)Tf݉3' L?q `߂mw9m</$|}"mjEB_i/rk]}⁖B}82=uKGa ` ҩqK33: @¦L+vC)ʋshdϵKݐ+ݍ&>jTK$jbnn=sRo9C}ۀ:Cgj60Qz]7_:7ތ!5J7L'.Dd Lp%YŨfCU;ҹZ펊L"k$zXR,ٯ.)􃇾cbMԢj o8Nm8Qeyn8$,>NHea٬?Ac'ؗ!{XBhspQwG:?Sҵ}ʤ ]}^D:H$~N:EN sӇ0|%c__I:,h__0eL<hЃ0ՠS(ղ|OF:Z\*sXX]$oix}aN$tW6%\;*}iC=fRQȟj}|8e QI-N[xahENLB^9+V&NnAۀ3WP&z]f5Z-lIzq=_Э^غ;hJ4O8Y^aS> ^@[J{rcaVcma)Ũ}(1J '.iHEhZiv*r[ cKZ*ן}״MI%9SjG+9j534f{{2[F uuJ51N9K2,8ݠիQ)m,>{߱riEE|tЃlN%tko?-oGEVNtd%~@P$I;*(KX$b؏/^ZcTԚ_n,.Z!9 .(HUp~d Gةql5TK}j'ԕޠTRÇ`k20md^&Z* yNey>R2m-']L,V5 HrLߥ\R\e= $UveX+0 j>}5R NĈ.=O%-+n[؁ZbG%A sM#@4\IS2_jg`4#gԟE |xl4p}r^6 Ѐ%7&d+UY- jVx9Pܒc:D$ gzt~̏躂k%}0(Ub9_0Μbt͐:_UfX;rG66^_2t~|㥱Ų+r1\|06܆KЉYURR̠ Ryb,&N,Jd>KDTp)(!xLCwoX~Wg-OK 62]zhM 0*Z+96 !{} H/Stu%' =Lc#K* n~IRiHlH]K6?6"9G7,5Roj^y'R GIJsMGB^,2DK7䪞C'F%HBmUc (3uy$sl/H`Jzyb(tJK6RD?4+W> F /ZEjSg'{Y\}3Y,]S ;ʉn؎8.FRBqFqUqWlB!oh) c ] m6HTN)3z߱hi xY>||=Lvf) QF5W^)WEoJ^.9qqKg+#؍0(5x锓1RՌW^S5 (o0 Ld@h0܏ճ"#VxgL7M[/F,ceR9 tq+4^&)Mf%)qxiМaOuUZLkw1yZwlȗ dU~%5a6Q1ᾓOup_$1\"[9Y>G7l?l:n=9so\<3wuKLkP@oaMYP<_|[rJF6#;8Tj_*-*+? 1^ڢrLB_]nO*qhp{ˑO el@0mp5WZ@QB9,7)]h,w:&^1do:}!' /OXhXtѾs_<}Q#({xGG6ٴ'WD7\&/{KX sԉV+2|a\q54C~({hr_XvU{V?1#:3}QZ`<σa(Ⱥm۸dz8/֖ٺy] G(XW~@!\v\H.Ja!hO֗x;LqLwTt nц3 h򖝒p/.vQ1MI%keP{7܆LV腷w=&0^V\6gcR؋jt:p[lWE,ဘ߼E`ܢ⧌zկg14E78dM8Zt+4d=ېu>~wե߷NnVV~7m/m{*|؟gө*~|O7մtS>}mtۨ\qO~sO겧ɾg)u|&'Tv|XS  `Jր66GڸF4ߺjAl%$a;.N8"OԹV6b7Q#UU#iWAR3e9r +nߚ& 7E%CǺ }nvV/- w7g@l=/j_mؖfOM_> OWs)ڐ`Jw <WD*~,USaNҷ;dmĞ*~@tyâ5 .I ]lU2͖ d=*>"]ۀˑ2^T$C:.Ƭ hNٳ  0;(Uarz41G2~ Z^#w]tFƕ{i* rV"BNZ=ko-0e8>E45j s"Yq5y=,3UUrDxά!{x}W*k+CC%Z7$Ygּ+i-G >6GU[wVlX1;U@t#R#g3n^:J1uC\ gm+HLFX:Q@BWnNYlO5E4(2j˧/0 ~1`XU ꂠBI@돷ExR9KdDBҼ %/#XfDhGo ]WD3'kFɓd-%=R}1#ym{51 /Lt}^GPZfi?7a%'>59dZ8mΎ~|e0qʓ]J'<7.KLSK l"aj /J} ވϨ!n.N?Z&oBn58J?|EtŻڅY*Gebd%.aAL°^9<7kITYIg2 @i؎ӌv{Gpӽ? hiQ|p^esVI 1 Rz+ſV>MRC?3oJ[a|!+\ך;l |MNո:d<=k9Q7~.j3/B4#oV 8D,K2zXll=']LO*N])$4)ujI{dQ8pmà}hCJ} WH2LHFlxsp eP+S w~=neCQ(Wj)S^s۟[`bՀ5 BvhY'}>lЧ g|g&I} ݈ w)h7|`\3v3QXHhy=k]Cn4CMZ0^l)pFN=[n֩a1""f}Yk߮3f6v2jr=^-7 YR*ؑƧ=|O9xۣ^/ݨ#'"(J=^ J ` r$6$2_PTbyh|VƠi=F$1C4ဥikd] q%NR^O TӛY]_(.<3(QY.u ߵʶHQs^qmOlF۬.U&O~򎪭#9~^$]0m i[Š)vX4pgv&NUSAO}EYN[BD>ifQ{Ԥ3rz$|}|7iZ?]KxK%hPQ %|1\`8 r"'עAĺ{#ut*)Pٮm솃^4yHQY`!kH=jtIGܔG=tΦ+Oq^w쾀x =B gpqDX֫/P0Popav Wy)Ǐ(+wg K [X:>&ڮ0 5A INnwIh ?|"&̃3yBT|P!O%P As9wK1tE9>77j +OH\nq{CGW+L@P4 $Qg¶\&}ubFR#Ll5v}BUK$mtmdyHڒȲ/?8$ag'#!T*X-E(+hQ eS~jdqX6~JI 6}h4(>]\9^|JfTia\6= yiNd6DC)k' #%ԬIJ]wh e:h]0t(|aZF66NXWXmxZ$@pIS0͙&vCD1 {Ffl"֪ LXq+꺭aW}[~:32,P\t!!U FmHhry%K4\:mǟD d13 t֓Rjyh3 ; MEaP TEs@˭c/t'4:*:بuCUM0/IH+Y0>2yU2<:d_ie䤀wQ~H8,[.AsX%VPcWy=Gsdyb s$A%\6Պ;sd, krW`Qb\dQIpy[|!;O$`2b8CsTCN9=Y/F06~=>S$/}xJ(cu]Dl^'^j|#A=dUyZ,1RZP;U*?Ҩg^ۢ:Gd\54"bKBF(zlY΋[g#LEfܭ+5PZU쑐0AMs}JӺ<>9efBӗeI 8spi\> (]#\̞q'@в4/r-T@) Bw\?vI)flP #QvH.4҄&G#<]f&Jn5AZ&haQSn-fsaɲ_&$ ؜g-t&hNSMg ѸSө"tT@`I Gn iĴP(:zO:xR|j6u~E37Ćz[U[rb[I!ࢱ]`-4~#Gհt- ZOD> hTf;9BB'ۄPLVY:0`x]بRV.{o]ǃ\͓ϴiKW?0۬:_@N( r0˧dx_Jm?LO1Vߌ9dfjVlfl^#@w]kZ“: h WJB^Xq~,TVV}ޝ99^#7fK~#(t'q3DtuF^#>a&}?ЙF6J0ט9; ˬī^5ղ%@B!Gϫݼᔃ\9owvv !D4^Ց:=]C-V߯&(!CuO! Rٔc]],|_RzB,v{ToˢԠ>3X`1+^ND}D(SEL[a&dVkx<]z(Kl 0?#$ԊӴB?y%jg@FY䉉a"rTZ<E"`_PiDvgg.@IWYl@A\m;_˺48(_%$3𣠨ETR?-ZspnZ9޴/}eED)-%-q ~i|IvgSrTLDR(GF%=z·>o]ezc㋴[$s&'"YWΐd8(QFfؙC77ŵ6<3h9ˈuDN Gq-\Y_4tȣ _QDcoJe31 db!j,¹~S!wyt^Uj _brrF'w.i`@[· 2X&%䦲hTKIMVaNJ ww[G&2KM.baZU;6-(PNx%ݪ|C7Ih\g<#i1v 6#UB8Gp=x+*[wI]dؔJVv˧Bbo#4l,؈GcOR. Jگϴm܏#l,П&*YTV" zSnM_uh[-{kjc/'"srr"֐~F1adKI8A+ ._X*xn[k}Hp` UCB[C:127``ړ^xboŰ=fCd ^z*fU7]x<۴(H] N4f4 Kˁ҆o4+hHU:^ok)/hv-}跜P\QvRIiG+n&< J-WLܙJE]eNu9VݴH^Je5sd ]!%t;Ȩv;'M.)).\9K9q$Ό#JL4Vm[U *E8;_͗d$)L2p.N-Q_Ī_Q9>YM(? ۖ"IʇI2}/);7AOR&m}5d#o0ra1zo_Q=l[PA]d@KUHf;) <1UYJvA",Y\~Ki‚֨7KֈR?^ĴQ)C+]B?+B_4_\Sh8:"L3%K^#m3[#x}΀Rft@'—9& s&ʺkK&HJUIG߈%'7)yBY"BӈnP_ /D:wR2أiiI<$FTytU^lńlK{P=cr̄bZEc$@ƦO u z's,p[KuHrǬl [&$[ $̉fқ': |eә0‰ ^+]JQ`ZIR-ٕ^rj>`( ʾcܢdͿxD@ DZ,8v6MJG'-NDVd"[Aܱ`C>J lGaz0 $:m2cfrSY0q)0"X;u 0"۝iV;Q}! b7 4V!eE5)#RKQ}G'zi{O[H Pm*u* ٴIvlI1q>#G/>tf ^MYiL/fٗrPfRށ܊JTs*&P!Js<!Q溬=8vT CA{Ov7Oɋ [gwAJ,I!B9挕1!t c~!i]nIˮ Z;f]N)bX)GnaRDkzjk9xzJ*55.vH _(^Ҭ>Ks V |!_$,u(G0VϯY Qx@H=Mjpn[Vl0zbvI d銭I_ZNEEBdhɳ:N<=' oy, 2s5LlCJٽ G-0:yKPmԕwo#.J?ȄFuj54gmPy:;y Lnc= Sbu,:cAGαF]~SQŌ? 0!Qnpj_MZ )w-9fٱ@N ~49OU.JDK â}LìoYI.gIyugi$1$7۴"*(_$޽8q r45AW;&?I ^`xCKǜL7Q`OEٍL/ӎ|p}TY<m 1 W9 --O-X~b b"YkNgšheXsEAbbnI֝{ãIޗxm^X44,F & R_ s3G{1a@, 'QoQӬ`2]|d\ Q tl;֡LEӋ;HWG`cu4KЁsd}s K3CLBVQr;?'w`H=&6>\Lo6^,_QOh H[TwF+ ғ3-P?CG[Blֆ[vt`q/e[S:ya*0ѭ7%=Z4-jTm_}eG)d}-pq6[U1*O2 q8πx!m|-aF`ΊNژh<d[uE+H;?Kv NsG'Ò.ѫ- ׾B&$+H'tm$}k,xV5٩4ɥj+j?U UQޜw" \#]OtNeBha,kffQJGi9j5sOLʠ1@~J Q&^9F,YÕřzەot!ر;$d/+茱5EiFKXM}ҋ1QSFABB{ ]-1(`_Vi/P؇x [Ȁh(XE6xtɖR(BJ¸v7.T Tcj v~6>LJ1v3ovIٵX:sqc1Q*Z\) NBuFVy^^M< G`58w<^g/wbg 2Qo0lp~%ͺP/Jf>=t /5Bcg yOe `h1bVTk xXqQqq+}͈G[ UnK1;%ֈԕ'^W[k8h!8+u˿r5QRTnV\hh".֮(c_)(6Е"l |`盻HH$q"MX( AMoD-% !%N ;f!/X"Hc,S޻ã9)6M#:Vu.ޤlBzNojHcڕiP QX9<՛cIe bv (\>iUYS"LK0ڛpoמ=pY3\G<JuljIn}-VO  ELJǧ2f t}9הNq"Lm q!y@d\AtmnRsb}^u =ZNHd+gqE*6+0EU.D4_18oq ,?QR?ʵw[ 1 *(=U2LjWeQd,/!TmO-kaAc?27LT#f-YW msQ.:b 7yC)`ܒyMsj-,h]_pNoOق8j<ƅ` F'y=f=OkFGtwqmY׶yض UD@r+.>MD/MjNN)a_Ց ~OȰ޶@qgfv#[W;7_Ho|rA1#מV5XûR}v:l; ٘$QFF?wYlr#d`ߨ3=BԷi֕Q4{?$J|{{i05>#D#: ieD6m]9$I!?;}Vz9b*='ɛSlqab2gNޮ] jT_{huDT0|>|Ykfݛوv ?9Eߕ  &j9s@&$Tl[Un9:["A({DF,f* <⮚ p;B cWt=F WtZYf֊ݜ{RH+.``ުbieIR# 6 [6&JECAc.`UDsڴsq@O酴>D6r!]ߘ 47ϖ;_NgBUG/1$LUf:yG )>^tZƕ"NU)f "7R/*ۅyU0A IOLuJ*ӷߣ4?jnԴar9qߦFynY͐9KG<(6(2tRP(*7h4 Fw ݀2"m0u@db0: ⭔GHkd7 N:Cm9JK@p"CM:L!"Wg=4x+@gf/6bt阱4zlC FҢY꘻ #濇RtN[NXӫ#-pB77˶4c59$L_]XdX'x9¤, VW5&ShfM`$Gȑw_gQ3`c60c nǡX]@+&!gIjYL0!v:Bl9R U6۰}JU9yYgF3,Irlc:_PlF׵HoHM~,LgDN&|K4[J«jM) @:ZN%Auϸ끬j1mjDh]N'֪-ClĽl<; TMO׷8x g2ݬ@=,Z*}twZ] grֵJW.kP@1[0 !v>3BnVF~'I@Ee^k7 oFf=spW4S 9?ܔ GCNYpaz 2Ya.̒ ك 4H䑹9m]a֙g@vMa!)VcFkK=w&e^xKʣ]m(2RB L9MX@aT,dpPCԑv_̅c7$5.J00ANb?_%^s)m8̡y<#P-hk;aS)n]גX1X}PWJj!Ļ o%8h  W׾)ԜeHZ﹮R[0iղՁ ];N3;-lx891*VI`׷UTWkW=g=jQTG]=Lt 9}[im:DTıtՐLRtQ0%%W$=/F}V2A^f)ki04kÏ] Jd8|N`5ڇǤ,)B#_sd٪XxTZ78(c Q+B/.6{]=boM ]aJʏ}{|QG0*gg.]F6bF Yt9qUErQ7Oƺrܸ uITR&pѼ-Wu!fzj8o Kxfmɽ%fY1/l$ʃ7i"B"p(eA 0@(BY k!xԤ *xW[;ߛs9 _OsrsUPiBo:jm2n#@׫ Z:<6^ubZO^(jdA}:}11U[9gQ sV^)ݦ_u͙mnVj$ W&5G4 ngNX$wA?b [pWKT{jVM Twᑅ$?~O=tL+4(f_/ blF @Y}Q} ('JW31<j"x:u,ctdIިAaf/[ji7",NՓFE@ /S&^38gD֗gݎO5cD׃T0jJ:N675R ])DC_gtS~  bON ՒiO38s (Y.k[o:VvޔE[O.#CIڎlӮ-k[5Et]٣5NM!XvR3NI7ף[\LKք f~S"߈5Dz-5OE͜|N Kyߞ 60`ohx*;ǹcCžDJĢP3DMj?#ҊgXEI/yV:%6TW~ Xs u'Ă촲JD2׫#(sb$ADMPhË.ZqN#ߝՏ&U1$Q9t㢁jݱLiIGk /CG픶AM9`Cln=L@##$ 05mA@xD؈eWBd03Eբ]︒ɍL5}38:=h 4pQy;-tY:<} 5A_$;=M~C +Born>Gр t񽌘imI D4K zh^(irהjB4S:Oz?ہU7A08N GňX V%7ekH>VCIIx~;%@C(Im!84 ۴1c!k ~m2 ,Gg T{"<]/~ؕL鸶P.\͌/P$n\nFnTSg'Y~[@m.U\.El(K0.݀]89n?DZ(M0Lݭ%lNF$(V0u?%m@ 5ԅ2p[! Jl)NUG6đs^C%׭>!mRtc+p_VS]Xʜ8R9Utw ci$G7>8lrx/VZY`R?4 VQj`< y-\%L4* /\^NzEyƈ6ư-+3y*KCl*7>Rde4ޠl#*y#aUveM㑴LS=]W8Xd5?@GaDhͻvkƑP鶹] ]?brdGӎ%Rn@7õKL6_gI'DOazz^vvɬɱoiDKæJԟYK.}އC7oOwH]ۮWuFhq\L ka0 E-+G q oGM@D +Iqcf1p"l6_vTNhUXPoB÷0%,WIDž5?v- nrSt R?kt!D 4U_+rpO?tѪO^or6o}l)bMcbt,)n˭)Ж+xx"G!Zw$5Mdk„X#sg6UaNp~bjy§fP ̀ ,Rfpgwf/hr'u۸f6,+<>_9'6%rHʉW{ngm-}{m+Ĺ.A98| i-oVщT@6l61^c;1#Й>DyxE,g5UVw#ƱAuЋ! -N]7…VNt䪢mUie^%$;W G,M.0_əZ8c:_VsUE9OmEF`N!:}'px"Ť"sVyz/(|e)L K/rfǥ@@V:E@X[ LNٵ%oxWL۞ps+ݖ6CjQg5f"4M%Y%PIibITL!W,ҭUS0R ;%9$=Jwӕ=¨勚:? RwKټY DD -~izř wFA HOQi'4١E1dN3xie =y~6ܨLncvQ'z% X %Ew=%8oL7 {^ʏ:畕Oڥ+`XWshyf[ni)#eT/J۬"Sl_0t 隓C9w̥K?#!^ _S?rD.Z/c)i qbX8>K ( t] ʓ !ώ J?ՍABFKJ ĩ'PV0{!-dZؾZg4o]N}SZ Dm=MUDt\$C8 0TUei!2,w1 p]itV2@NiVKTLH5dO$"wfeϾ|؍2U~x)O/,%HI&7~E#ae (^$ ADXpH{jRَLv;1`| l'Ðy7Z7~@f48 p~k-^M>{Ih`S#6qj0-F`4g#z'wy$nDJ}jK={2!:Ώ3{6Uj {-~2%p/|[};vTٟZ 9 ۺ٘Gpok7ޒBQ eɑ_a%t?- #\>oxPGx_P>Xg%r/W.9 ݘ3F)dsdxj2_LIĩħ=e̙)MZHygQ\ ' 6GU({نIU* tG;)y5LAڪ2dXd+YG@rÝG'NDuDO]ƾ:){ˏVPv/QL6)eķ>9YcA GuSoL4jtN 7 ~65cG1Y=p@~:Q!{L9)z\Sё PK>QѴx$0K+2ynɩ)E V/[/I޼sJQǜ81жci51-"ޚʨi,u)æ#y Q)<t<]w1E_@-:;ξ{Ȅf^H2LR??rbi"x'ZŅ}I,rqu w&'%\˪Eup}qy zV1_FO}l[PIe\e =0- 2U]c3NFV 8喎ƖCʻY `vJTMΐT%G)ӿu+T.S#:?BG4o? *7hoϔLthZm}:K+CyHM|p,Ƕޘ6t.\q4ѮXz{TDj;rxυaVc ~)Dei9öȠhc Zm;^Wp?* g>caN|&%eV=EeM&YAP@ٽaۘMNR<`CH|d,)͗j,0VmI< KyHڣs/'/!Ǿ#~WkYG=`d,V9N-b=@Zo©L [F޶V!F\mFkonxd yAy+T>zf#!GM>#;S~ݧ/2Yڿ΀lȇc&xLq~i_7H_Ҷ Q|]ͽldY .hX]x.vQYQ& s<25a`{ LqQ{! k 3ucΑ4El\>{g5B5l"dϵܳ#"ڽB]v+keʨ)C7^X!Jk< dؐYl:\`XHb3l/GPPʀ e F~;W#.s#9¶7I/e LJ'șndha0ԭMKgWe4$}sN  PfxGɫ8(!VYέ#tu l[8%{_;*W<<0|~`g"s}ºv=!71>ZJ"p}10cˬ~_A;k}l n]p8{p#p,ssXZas\T/SAF)fCs9MFdf80ާh1F67CY<]򅄢9[Բm1C `Q*3gʸȴLF-ӟw1&Ts7MpGޭBKNGX ێj~$ƪ.ic+CNcn`Rr d=DcrE^/yBv%krH ?DB.GIODhN]kcjDLDJVw=y.#kϸ@_$"H0ֲC$O,?>q(-/`Cl\5O˿nϮjO|SkC"C6%\x;MS\$`bN'JZz/x^qKT}4KPWdM}SI^>{2Rr|W}Y/v#1G,_e'?s@e6KHI j_K'aծO|gh(w.FL e,PGF>(9PcX({6$ blt-i zi(5Qܟ^"f0O@T=焏 } )-"x2,趇\@A_M.D}/>'PSgm :\\=(6~3?Hj\dA6 ]eUp-"ـu'W控_-[[t|ȂRc $rUID;=MKQwxt۹7 ۓIk%^ 5 ?3> i/vBTIc)4F(>f"4i?0%\qb@ܓ<{Dij f8☆TJ҉'Ҵp.&Pɺ KR ~Jy$tqK/uRȿmWR.6e02<$3U'KW-`,hǰɝHK˓ X=V%_ Y>%A~[4):M4sW2̫ ҵgpk>tPp1< +Qͫ۬D@qdkɩ ` wGۢ{)gҘ1ln`okU ':U^# ~6z$֖S 7%LF _G6UkhPXߏ=Ac[1VښRD[+ZxGt)d-Rs܉G2oURQI4 }V=16e V;ɛ5W烥he+[JV'HC }*Nm;GstZ =eʕTsYJ˄tA#D`.-oODE(wf)vE:эj:$6sѰ;-[X4b̭O9 l-P{n5NeszeQ36u霓*=b}t?> 򇠀S VMBx,Ct{$6[AZ%4W6W_(]/çuހ:_+;1sWvu~u_ÿv}}~+_ìp[hBb}l:߽S׭{&ƫ$ihڻƺF`bM(+rs3#_ܱr+[⨡ǥ܎믅^0.tʁ\'xLľ]c+%+ "Uk2xW(:PÁRy;w~ĘMS"qykyzr걢Yĝj`/GUD ~hR/W9㎨{^kJpw^q7a0iGcr $֤? —lRiSΤ,0~]@n GO)O2xtCøSvyi5x7ݞ<~();̀K%N@Ax0s&pC;2G0%RR0/1,@zx:IZwЍtwgƞrWNK%/60k؂ n~vle J .on`Fze>QX~ 8zFb 0+?|wOQfTR*X(yn!]a &O']K@,}*WC%] wD%MSؐcT8qŶ\xmQ3&Re~$3::fd&YڎWp-M+ p&:S25׎q4.2ZY^|ծ \̓a+:T%u~9OO]we`Z%)K{M)$D-;_t{+. OIɈҞp/d{)e7&,R@ku/0V1Z4so{=[4Ud:*_-ܼYiNvFJgA[h+zmJMOp҈c`G2;;n"L /Q.c+B5}WLCDss{PaXۡsW[iSMpQ 05a}-, HP[w-}Vi9g~s/_Zv jP+d4hNM # OjSFPD{t*١Mg4lcbHB%}\_[,.D\ *pJ.% 4V=Dz:j0Fm1&P ^ET"9꤮$qhDq`e hAgRY\̬W{pD2Zjy L f_qBywbza+^].{)7N`ǏÓuRm8*%Zz7z;ٺ}ƓNl d an%k?`FPaHzR gv ;>}8~TQwixz] ꄋ h͛xu;:S$eQ3׷&HtlѭX}Li'.k>h+(KXҘG'VN:?_i3֘W_fWiMq8'ZZ]"+) 9933B5(Qg<-ţ[@-"yw3C\X7nmǹɔs:pN~V 着پ_8m67Iy:Ee( /&7k2_5n>+F|{5Ol@&l弆/eXH_ QU!(Uw]f-ON t+bǛ["YXד43aɯGtjȶ3q*cGZL?)wOO1䍏 ]40S/ i\ORO|*WK1l- IJGo~KІaJ3ջm3fׯYRXC?iX"q2@&;3H?r0y:UZ5ME} rDBs$ (s]~'|Cz#E{v7~ѹ" Bǐ%9#]m}؈nУYq uMYQ=ojt}3_v)B9C+)֥׍\ڗ'5_is= 2<()7WG>kbm74f_ͥZ3ϝ~˔:^<- *΍6ܘ;4"BF/ R vZH|mK&Fj| 5o m-|T뮿0r̉zc@Pacj!bRV_O,RX~y>R~gS|?3T~$܌]vyDLn=VY"{`edZq33(7#9[.N?c~qΟzRM) [?ypDmG$%@^pNj38~a^z)|5igGW>Wx:3 =T Z9)Uy$ErXB\(JMfJ,_nӵh7[:E=dMC)DοMu471Ao+iZޅ݇lEEB`[a\9G̈}DvX؟GO91*^E"ZRqQScəblj]#?-5{.\2 X&QYBup%k;!:iwx17ŋI*{0. tJzMq4+d][x`6Qm~Q)3|dق<-ME.$%rG.tlf''4#}:=7@"ٷFA2B9Vb3GJ~TZ=cS2(]57ؤlw;v7<0)[<%Nzڢ@ƧKɱwgM'tFM!p x5QC@ 0iZ̞7`K9N/%DGn\گwj~]>/$%96! URok=%Lo,"#x)#J;.uԋDFs@vz'9jv|wS냮¦~u84b8rOo;^.Mh % UA(ۖI\#:{ά1dU-LQ`:X=i F]BCOQ|T4 (N@0)P6}Pܽ-~0rk[r(I.Bv=~_Vv*H,,iftin^P%U𖤦Qai01zw5"G." zQn&+.)+ ߳ͳ \xжPc %j_MDq~:<UC~nM~v,4Y69/v, H+nڼM^6o4ȧ:[{$ŢsXE~V0!E̷~|r7HJyJaa9WQrVdgz %fSYYy/Z`a/HKXw)JüYC,Tk>d;8w2T;'W}ōG\RM8@" k52JDl4zŞǭ?b+Grc9{Y\: \'ʺ*gA67m-kRsLE2s16Zi3vfZ:8#K :;3(zIyK+~-(Kb3}`fBE?_P˦ieTxi ˼4`)PFb趬g$5! W29@{jw]Қԅ+ˋp%U0ElI>Aԉ9s LVK#!Q,Fa|[!IeU^T;9бV5W8j9W.`Q /A֎C"ǂ|gU mT4ZVEG䄍_.>h[ IVy_b.fa!D}%Hk|-y_*13'&=O9׍/ٯv%xwQEFHe7{Alrm#l}h_‚)kZLJKC9Ӟmw4m#/-.~qư$Հ~5e ]`0^yt (qOu £:lU(_F0)HmwRL %:t5g b44qZ[IIBU퉨gGcɥ9 )MMHxF('jvj.@]Du&5T2 |I C .qZhq7U@-J9ۀGzbb%vU`nc K g,dsJ(&fu@p9*:sbŎ't3p00}xVO8[YyTԪ,7JGSg?g&`*@ NƓ μH{W7ֆٖ/}EQlFF!i$)!o=kϽV[r<Âc7AWZ& 9+cP)O9~i, rZ!i/s\Oݽ DbSsF+K}mSvG>4 hI ̅ -EGr4CY'EaGZ 94zk->!5'< P7%'<,:yuJ<I9CU쾿7Wc\%EX9kceu\vp^ksa [3957A 35[o _.W6*P3qªD %08+684Rzj"Sm$*pEIC FQgOYkiO$ґbrw-~`6 =oWB&ydG1$$+ 0h|jg8$ PV>F2niQjt7!R∅[BO=$x1ΗN:)x*fwxuIAdFM{()PzAKUr;!Q-j@I/ereFibջ&oG\}ne ?dm7Fܖ B]q`o፪ԙg®Z.3׏vZ}n-eF{ T!V@GKfǎz"ρGx_y. 9%+4-@}+- ENM>d<<勍^Fޓg%6SxMz+ Aq΂uJ_8p|~~?&ov{"&B1AiZ\3g*pc qK>'0m?L*R!̛GDZD >0i_|V%9 H(d2~n c(#uXF?=\JaJs2 yr <Kz۞V3#ucE*en::^ 8.PC7[:d"5GBz#}_9A{'lg4J1y[e"w.XustJ(+}rzM%0͢C<^:TI<>!җu0vZT'Rqe1~ɧ]wMʖj*pE_Uy$f ôkKus;U??;9Llm ȅp GN$6r7#. Rt+fF2# GRb.%2FdQCĖ1EVxrSvU{i1eLo݁fvg"' ח2޾4HK_0Q2aK&wTOXK*"3y&ݷG8i'e[!X4v4vTA6&2r3acnYKIpxJN u~JdwA ]<%_c#|HgedgMKz%#U꥔P';&/O+M)I󧛰 =z⣿/vP=l詨^ms= ر9Zg .lT|kTkjOYC~*d0IX j9!h4a:{·fK=Hdsr60]y9pxf#dx}gK5ߦbрMp-[jr@cL'i ;T9 aoȋhJjw፤x~pVÖF(ٲAMSaftZ)ܕyZT􆏏$oݱgVvp!$S2-G g4ZU=QkR*{8+P?b0j˂aHU&zh?YLjsrc_ӝf(A?x+tBrH .cg vBsbKpSqʬ8 w؍Ƿl͏P9ql-s#Ql/d9SV+\G/fjġ |-wHD, ǘ'+e<펿7h7W кf:)V:A'Ε+˩s(U H7 2n&~7:$:7GM-(#=00^^IUQϓP[oYp3p6mgA=yf|oiɷnn'PwSoV 8-^|K[ $%ᭆtT%uxQ2q n85 7)Җ-hH%>b!buu4us(2s.ŸӔMi36AA EZP Ja¼Kn)"bVg [ΤV\T6 ,(1ɛM fߞ'D޴,´#V .D/27:a_tθ[=fxX49M<֣Ip(B] 4`q5")Lyme˪3k%Yr0i9Lڔ]?IB~EL,%آB˦F3V UQNFZƩa$o LQ!lU-ҺK_}H@‚3Atc0]Ga쨦,[ܩQo<>`O,ꛍSjRN4m)❎0|$(_A,iq7{)<RrRH |<5X[糨V,P ' H.֜pђG dprM;̃==ڞo>r{9CD'asn&L`6o4^d5 mLf|6Q H'z.{ԆUC3܂}+!kLbq)bCX `-70trZ[{G{2M77NXl lr~y `Y0Kv\HMAKߠ߻[lxK| "R=:>=~cPE};\=nFl6ycx#z ygXi"Aߴw~SxM6^%w{/nBacƱu 4W*h0J彘1 ::/ 1{F+vg6_-czI?m60 ǔ:JG9݄e}w}--o`'1RkX#Ȩ43:y7 G-woo&XiD\p ._@" gqi{й #!yuEbv@YB >U^dB3('ʣ`%X;-'{}+%ʀ3 *Cim.- Bh}hdEi|(hW@b5PFLմ湍)g`91\H:fsMiEj*w/uEeU >$kGd&C4!q?ZcdsXQѿV@ sǓG<,϶b* ۭ~h\+=>p CDK*~裓rAF]{PBƣ>\ᝥnV"i,)Hz_;~ee$PIDQ;Z$ }- 5M*Ĝ˰;^/% EdPlWL㾚oy [@ev7 ۧUަ yhT&jƥk), }h(~֨✑hBhI`SXDcKR]Ưyn>+4PD ɗULmVBRCkA(5-CO R_C֊z+d;b@aU!RR1ӶnWN<' ę!`ff19Zhk] El.+,i `;+D * Br%^t%-5v/oLcb B\y4D1-oOmzOd[dX hD)Z_/KУ{dTd6VG8{M#1")o&w$vLP^ yz"{:$k*ik6 H X]] :"{k;aV:;-9QjR'j87AkF^Te a5d~*x]P"H4fTr XVR2ﻁë+@%0z2®1#QL*Oolg nJ${ui - <"KkfH_4;?v^??CiD IG ܩ){[g8n`k, FJAh6x(&Py~\݆dہ5^n^hLFñHw2ˈ5dO<,u]/`N6Oƞڅ󲳋U§@ߊWgWA@{50T?^OyuFH'Cp%f)X@0{#zB3\^]B\gU3oUښۏz?8>=<{Ҍ><r/8} %s}=f]b?1?GS1MkLLN*r=|SCyVx?Nn6"vWxwQDo=3$M'ld?7=xyQqHd: ߙƯЗœj?eWotR,H.AY=O]B[m ~ޞ_/>~ ЁV3>x$9Gsަ$F  ,{i0~nP>7f?_zqP1s~@Xsw a41Bg|,'ho3<')#~q}$`GCp1Nx=5`@p 蔣siGFϤJA;qDuwàF7'!#{'N4,})7g &}*QBpͬf"h$0 4'eBF39JF>#^z|m.`;@CLREZX-τG+i|T#oNw>b>82dɏEXggnٿf)UN^Cm}x|Ds.;L>1,׈>akP%gR{|h&R&"fc$0R[VeC즠,]aۦ@g g𧯽OyFz:fi&/&*gJAH %nG~׫WT a/8%f4X*(GQ]qV[8YG@8Jl9#KQ*#iR׸eE/#r#B^Gc}뙋t$FҘX. ؝|ZՂzoB׮"LEFh(bCvMtP̦SD 0Bz |U͇sfSV|{9#.aMs`0 IF^782JC|d?JiwnBM`ɱUssC*g "1n/ڭ N?k$Gl |B[i&sT{!xG9al}ݲ~c͸]f?]f$I/p(!qH"ͥ/,?BnM7%aӦ.+y( Ћ2 O_>0HVyM^OwMc4Z< MEG?$ڌ|~>6Sq n#.PVN;je'Q{?r{o$cf1 ˰DYHYww~#xQM@i6  wञZ촬*%67uJ@S .+/!yj P4HjEI!Ojv}449聍,"fu, Xעm ~kvWqC!ѐp4h~;u/ 3C$yU@@! ! 7@37A;In;v?$Qᎌ1?*ENV?\UȐGK1Oe[%լcQٻ-\bMxU-檱i,#Y1dDp FU R"h#uPc_`en2+!(wȵCKa.H,>Lzۀ)Lc}UiR`s!U \X`HQeX&[f.>"SDP[Q=+l,ѡˆKYIW:1}9DӢ^צ-Xd֚cl%T VU!Ok7vB<55y5YwSoL8#! T0ʨiW<$qZҩH!PHT uI\UIlæw-$7HKT[(@h,RRY7K^򹆔R*U~s"BhmfZzL,I?H5t%Mց `8[~P^SSIPb;t29.4s>fH#w1+*"EzyZ9?^\BaD?e_DDNnһ0Ἱ@DMfiE.$XLJh Ic+7 IDATSmUAemܲ;2B3^ꚉD2 <#x yO)NZ% s3M< 1<82!C/D_]pVKW#Qjw_ψwм͵frlH%\j]d!lEcOwP9LT%S֥d63[a'jكb Ǯ*B#l#AQ:H a$<_ÏCp@܎aFXfiqZ}sy(3I+Zkeŏ0*45q ؑufƙ,)R_*rֳP%p?>oG8PB\(/S `-eLI>,H6KLa/x$LЬslwfB+gFA-B4YaKY?DDOePN/i_ahE%wRTqV#x-=n#x:}HxǻDҵDOU}:b#! V/xP~DVxbZ[+]Yf_-J.&2ٓh!*l3 3s30)4|6럹Oka;-1l&2ED*9t`Gu>\ 4T5:1hd.Bj %rNxخmh҂Xɯ՘|~m;x]aȐUbtSt & H, bp(iHlXH>LhSr>WpDzR5pv`4+@6rDLء!(A`+ڈ(gUn**K hi+++M zvWgw ?ӆvM4JȘ3 o.OzMD=VAd\q}tJ*?۩'"HB8^Yy^giybJiF)50 =T29+~+d:!A"jE-wx'Ǩ_񰀞kpym0D_Q9(UA'HmR`::gP:[px9&܊6eG %@N]H*J])`F&H%Ԟ?4=sDD"5HjPudPvRS1+AZw rf `#彳ZdOfWY? Ȗ>QA>GJj ?!)zBcdԖ^U켂:`|>3ߴoyo.&܇qO \4s vFrAMOuqz=͸qcHcWƮ2hcxK$jٺ|~7cHI osafVe~ W޸XyᇪdGyqY*-ow*n(R1SJBYT@ }:0UZJGFT,!2ҒZ׊#%,N0垰{Gml ZGkz{Vӡ|DD:e mXq|~-E`> E09R (hN?ַ48 6}\î]?, @-[qJޑ͞D4jHla04(nyŧ9_8DVJ Fytfe;rXWflgQŕ'AVcנ.΁,z||l MC(1gzMMY ^AI%@idtqH~շ'F07^LH?^y)IJ˒US>KzVT8ty1v?t#u{;T 񀓜DrT-!ĽrgRDx}#SRFxgz]/![k8ih9G HSHa__*6T38m"ĹF E ZGQ7ub(ӪtQ6MCLׇ]ӅkOJ'JFwXd諸Y-ƪ1^%#p Z>Z $OdX+%w -l5`ax@vS~:  c$#G$(#%F*0H5ODH{nl:̜%W~O:{ ,Uk6N]o2eԨVҗ=!{ymI/HDJeht"w>@a 98Ctc;ѳl p~% Vsf쮦,l Zڊkku:}rJN/*w$"U &b$razսHofë?,X t7ʈƘR޳JjGtVyR4;]q*ߩ) !2QrjdFZRZACGg>/di]RgSH|"jƮSt1ygQl,FJs^3Uy6S"4%*4k ?Q;Xj:X Tn*G#Eێ&l>͛jb$|cR_HϷAE%YNrksއ+/3Mkz8W@B_ni>~49 gyK(`SKӫ|;) 8LzƣF.Jwsg2rAtG`%B``+׮r(TE#i0 Ah:{b`TxɬsZ8A'E[ %g<=0[(jEթEUkKl@TYby|fsT/^s,)FDL XQn8s'no82Ƣ=bL. ifҪ@rsF6[%%9m/ 2+#bLkS:x kYUܭYq#=p^Lw#bH>釱m1E4TBJ>FقSF'Cn&x(WՁ'=X j;8 y՝v5yfElLjs/4h`HLNxZhFC.]FzKtxgesRi(%? P4+[yt2N:tVJn{gDd|ӈC6/;p>c"-~Y9cջ3^%Z1MMI*4xnA9$N@iD^S )"DY۞y5 9)р7ARnWBGNeEG.q? ˎuj@?m; Rdf })* ]xi\ԣ ቒTT70PG mPU0nŽF+p$@o(%h@+.DTz!IJd@FhY7 mY]85 {ǫx۳J7ɯ-@SLp#K<*p:2MQXH]~ ] ~)lILN9.hs4o6 @!^XcwVu톹mlvSH@a ǭۣk\e" < otJ  }=Nx]=9;zݐft" "5mW俔_Hu\;޹(iVUYIb^ IDAT0 K*]TGBht96J (/h|$i}fTPC"'; ^ݮ)@ g_gK7Ro5[Q\[/ "!";kM :8ݔPh:ޏ`-ps"{_@̻9|%DNjwuv"N;%e| T!+"9wfŘ7XiFX/M@YkEER{ ˏhQe|+0vx-c=vC$ʖIҮ-DF.)YZ2 ؋jK ;rԶTz.JHBa#*815%co/`I ƈlt w g?espe\3 I7Wj_p=^ @Kk2 +Rᗑ_p=6IENDB`(0`wwwwwwwxwwwwwwwwwwwwww~xhwwww~wwwwwwwwxwwwwxww|wpww~w|w|xgwwwwxwwwwwwgwwwxpww|wwwwww|wwwwxwwxwwwwxw~xwpwhww~w|vV|xhwwwwxxwxwwxhwVbGFRdvw~ww|ww|w|VBPrsCCCHwwwwvt`pwwww``vww|wpwwgBpgwwwwwwwwwwxhwt%%'wwwwegw|hwwwwwd6wwhhvwwwwwwxhgBCewxwwwwwvwwpwwwwt6w|~wxwwwxhhpwwwwda`wxw~w|wwwwwwpwwwt6gvwxwGFxwwxwwxwwwBC`wxwwwwwgwgw|wwwwxG$4ww|wwwwGwwxwwwwww`pcwxwxwhwvwxgvwwwxwaadwhhgxwvGwwwxLJww~wwB`pwwwwgww~w|wh~wwxaabw|xhhGwwwwwxwwwwabVwxgwwwgFwwwhgxwwxwt%$wwxw~wwwgwxwwww|vw|wxwwwww|wwpwwBpg~xvwvGww~wwvww`cFwwwxw~t7xgwwpwwwwt44wwwhwVGwwxhwwhwwwBCGwxwwwCgxhhwwwwwwwwt&whw$www|xpwwppdvwfwwwvwwxgwwwa pdWhWwwxwwxwwwarVwwwwwxhwwwwwwwwwwvw~wwpwxwwwwwwwwwwwwwwvww~w~xhwwwwpwxwwwwww|wwwwgxwwwwwwwxwwwpwwwww|wwwwxwxhxwwxwwwvwwwwwwwxwxx????( @wwwwwwwwwwwxhwwwwxh~wwwwxxwwwwww~wwpww~wwwwwwwwx~wwwwwww~wpwwwwddV|www~ww%gw$rwwwwwvpgwwwwwxwwww`wwwwfwwgpbVwwwwtwxpwwtpgwxwgwwwxwvwwwGwww~xw~^t6wwwwGwwwwwwwt$'ww|wgxwwxwt4wxwwgwwwwwwvwwxwgw~wwxxwt6gxwwVHwxw|A'|wwwwwwxwwVxwwwvwxwwpwxpawh~Vw|wp~wwvgwwwFwwwwwwadwvd4wwwxwp`eGwwwpwwwwwwwwxwxhWwwwwxhwwww~xgwwwxwwxwwvwww|wwwx??( wwwwwwwwxwxwwfvwgxgGtgpwFwxWwxwvwwwg|xvGwwvwwww~Wwwvgwww~xwBwwgwxwuggwpwwvwwwwwwxwwwwPNG  IHDR\rf pHYs+ IDATxs׹-{Q5Y۴#'rrrNr8%uNիwMnWc'1O-[e8=эFHİW $^߰@AAџ .,Y:G5@=799;J C3+NX,BVȲ ,pU{5-e?QoZ`YBx>!LMMQB0|C@,CV{7W4H9: B #W 2+qau&lbV`8Sr*K(P"WWWQ.(Iee5(1``Y^نmG36AT(ReU(8S06H\RXXX@ "JYː2r|?a!1G߹[\RP/XZzM4P.@deQ3S 33Ӕ(޽km@Uh9 ~āS3D8TUV.Brf( ի/a~~%uAeOwE :ȥLU.]ĵk(P>ܹsw8d)P2]F5z&:ĨoUjJ}b0^{f(tR7Q,š.黉8d)#EU!fp-JVLb|{ۑ8.r =KU%_++@QU0S=+< Mpd7n\YJ3|MSAg7+T/-qEZ_@ xٱ\rΚ8Lxu4P8ᗤ24MGlpQׯ}> XT$%J޷":Xb~G15( |u (bqqJ@QUپ'eL  i )Tq]kuE5G ]4)a|t?J}T*e` <5. "uclt?J}M\6͠h; z^( }<~غC%P>Neab0WnAch"FBu DQw{>G,1J-݁RGbQ @ @/zRŗw!2Xڟ;ӓ=)9s玵BY0<8>dw4:wyR. 9 SH OQO) N8_*ˈ]KP$uxJg%k @J.v:Y x?S<4LtKhmm(?ř Uד@޶ryr# Fצ ~0v`c7DjIT QH@8?9U]CTPT#sS\z{`ֻ@WښݯQRW@Gj q;4 S|m7 n"&%_ Ft Lѝ{26cbxIc [C݁V܃t%N"F`KE_1xA(mGTS4@Ԫ"'Oe?E'㨄Љ$1%`t%lӰ[&DŽogQq_IvJ w9]2;DNvZ ޸|KϜ~ZKO꥔KCɭ g~Lgܾg`5~ ,tHʸCqkvv+X\\bcW詾S=YA#㓊4zsx);_?}\GJ_kP ՠO2kP6YS''ݧ1E$=x._ޭ%GhZlTO;$_}ܹs:޶ԪR?O"V@"<,ܹs<5X\\Lq&J,Z7ItTp~ !9:CizzMv<3p* :G-Y/d)U.2M@Ű?0A6Ei aVͼa$~[dp ޷Uju_SdP(h2rrR -aypebXZS!(HT( <CQx4dӄI1Vv ),?[BnWc [fdͥB ,!0u,~#} 1x FM B0IT+n|QVoglI*dv30X(Be0lsq4b nυXUc@ts a)3 b<96wo["1\Kc9{[)dsiTwV aYpÊG+. x 4M(,"b}cB,s CMN#M"̅)ÌD²,{Yz:"2[x16 > _o7g?6e _WU&˞g qzQF|tu!!N_jxn䬂P l+=ӧ֟PB3PgYjY #1~sLϝ|]bSt5b`86 kP$Jd >GL,r>DqU|Ԏ3 ._?',..Zbx*&vHa}q`vHԿLϩsVw :,׈G=%\>'<&tqX`b@xp,XF,^tIA5@Y/#}f}ґfߟħ­NR)+:ՠN"};FstT8K< TnHH)y} ,gY}䜅:gj!"QU )80*NRN-n4f?%ӑ|~~aƯ)2?GOEҔ< ``8;h+)3`T/߽W3xc Y9" ,s!jвqZ︩N?Ϲ+mښ5??t,--YHy^w< {Vޕ]X`B2> QW9!Q ݪ)t]Hu}1~/ P̊ `$#"Uf"hda?oư FvcĎ _Hv>_ beӿ / ΂YIC!2s#C ">4yk*fwp "Vg k%(6! ?*0*\EW[Gu;ŲH| &2&7ll>H} 6xww%/Oc'w+>4$f`?D1TfRa*QZ 5D4zN:K,WU1ddַhBC}cg_QT UƮ,foD_FE^sD< q bnt^Ň&qmh.- rwh*V8ÔY^xSF.Bl gvvvܴ_#oEkO20בaF՛ǒEr?v?W8D^ucgtMQG7vK V{[#x,ꫯ]Ir&3!#4Pwg _MpqcxکͪM%}%ri8 Ƶ #;iPEʃiVĿ8}DXJ ϪW£|eY:'"F?60~ri0,^~ sQvCZ@Fq!8,iL jvou3N_g{G Us& k|arF5T^f;/uٖ9;?\*g44$0Eb^<}OcDXF}Ri 1>wo[G9-x$xۖ,ꀟe;?_Ȧw|E=^/ޕ5). JR} .tS7@o\y|V![‡w0zUrK_O vp.@G!uHGTG",?~zauNIm5;c(>/y7/l~E}r" Ԍ߻^U`AsVaţ/o3m%'m>/Ky|t~{!J!XՄnHWՏ_eT}ǎeii-8jY"Fn8N_)˄){x}!s~8;JR|4TQI_t%'q[=LDx_- yT=uY~={Df=Ν;xtla*+-\%Lu{ŻI{e?SmXfw6LJ`QerR.6rӐ ; -J`? ee;;d#/1~}F=9v\?Up\$43ŁrpfR9dw[4R)KB zQ |_Σc(?J=㟮ɑ9WqR;+lmٳ E'D|n@ 2Vᨯy)Eæ4M< 'XөJ{X^Kyߑ_WTǵsDU|~%%8B`"MˇJ7[Hʟ!tPcs8~x.@PZ RF"[`?x=*kx^ݩG!f7V@?,,( <@$yNU X&>r׿+ 8Uу;=| Vzw"xe*z5<|~$X+WLFe=hNy82/Rxu5HcNu K5Ny1"X #<&/bC{HC@"aOkL=+`XX$ \vh ѣG\F[ riHm,oD"l%vX]}=,tu|a  J<. 84 Ļ֖{wyCoNRY/uԢ8)%5:;@swY1RsԗgA$Ggu7KxWabnyKXOfV=ﰳJ,EU18ܙg[U篮>@X=Kvt.ix'쨯\D|MKi0g 1W07}TEđ4A j{KhZSUg߻7 : +^>א<`df)k!Ē8ȥ']aV>iM s ak_O#MA.!> U=Yjz<1۟1,XvM:?v3J@Hmx V& QGfuu@&Ti9u p(j*UVۊ#oD` "aYv,@XVZ3DqhT`/asw3PTzw{v-6#CtE#rL[ āppH w@?tq6?KC$aa>a.ɳ@>Ì+gD[EFC3/@h=G"W008L={f]|!]{Y&{UPO;+0 ,̱;7DZcE"+E"`9fE LMs5́#6X B4._\ @H \Ė7 ]?.TvX4N](:C" E0)Lctn?!e%%C7 (.ښ褐)+@<#|gy|{\@.TF $jkFV g Et50G Ur@%3<{PUa l,*D87:-±4$ MN*/_ #0L~Bt te{R0+M+yI1hvF?v`l,N (@$$M"D%:x15_Kz*(K͝w~vzVZROWZSE-J%b^ЈTFM>ž@P4Sip+2-0w"|k_nGs{){`RhR5~"+Ek۽E,ލP2L1EL IDATi@'ct5 e6LqȲwUE,;yl6] +iPX ! %0c zSڌOiƙF5߬; @  ePP"@@T)}`M3fjZ<^`-m#DUuTer6DElNeh e״8(sbJ,"4{K}p,1RaUI?P= 4,!G~˳qP_ptN*m-@4\8D*0^^ keê,7kwlfs+uP$`#f;o᧐Jy2cG"X;8[v`pܦkAT-o?`|s3W >LHR; O9U!1)ͣz4Ok;'߹-K?CsA$DDo78xŢ=(=.w0'pz0tb_܃VIa[4 I@ =p\h;deN^ *4D|)^y{4pX Ҁ^qb~_ i5*-c=mxXhB4 2L˄-7$(\ 3Ys10%`tXe;-0,fGc#6a+.e2H a{ 6,@X PD/G18݀di%ib :Q"Qnྕh%F*wRʓvbUBc M ?P ᰳtDFDȺ N ,f5zTzcXqxL,H/,ZuV-< eX7ELԺ"ٍ’ji t4~Ԋ7j V*J;MdpC7@bGꚈa 74@ Z2"Ŀ;]C: -eJ;EU $1Vd 1]-=,:^Ν poDN1PY.ѕ?_Ua(#T߆nT&F{0I&$ tv~'x  NW/6ϡpSQp_T1"@4H,ˁ-:ՋJpqmމ\'#l5ċBY_1PXC!E PWK0 Q[OZ'9 nA0#tHW:LruĢ"҅b\ D]Cw+u ;Oj=2"l p 1p,x@*l7P9;+QȕEO\7K ej ^g5SD{*syVxJ*PVP5l!0>/I\+k`n$KA i9*|59qw0fHh|,si triښP$R9 {:?7~,.O2d_j!7h[Ys `hh)c E<4Pgx.qP+{m( NXC^??-fGc&1h|4lN!OC,cS14|?뱱1krtPs;u<7wlwB \0`i Tc2#M܊ ggdx#_5:P"F0;; wNNE6ؚ@XI8NɔW!|l)R*ؾ3}-(ԫ4-Mb9MZ - :MߣPW=SsV][.FFG'*`rU0S4@{OlM *'Z:6( |?~_`׷oZsQoql0G{GV1,GQa ".AaaDV>7I.8qE\qlnA)I5* k8H{ip,]c\D4ˊs&#o~Op՗Z#`NX[|hcSa6e 6 k( tXh"l 9U4}6ϗ>f`i@cyP.4kט>R'<]T1G5E8sKU@_P| ?Au\|Fhp_?Rڜ43A8 rOiZBfcÇO<l@0ya) @\aVrIrG =h(pfv΃l=7ug:c^LÀЁqZgCMo3^@<| aX…*V^pZY\K>/xޢ[Ù `5FƍX=Yވox?ҙ2aUWZ]V 4]ž$/-T;Mvě8iqzz6E( VFtpgg|h245"{la#K"10d?_S_v?h 3& @iZqNO M+47sbX0iB0/=JF L 8MCa?' f{`M_} !SW G. wluڲBI Wn\}.LF8_f&lPPx{v3džK Ӷ*A?_*1J(Ct[c`}gA.*; mTvv`sX!|{_, h>lMO_ MrG&^{bOړO\$ FP3^)n&%%`<@EIO~zF^k?\ ن~W=S7hЃVG"IFyKɭt00.DOLO!f}>cTg I Ww T^ߐEwj HP !<^{y G4`BҡAqI*w\^ O>Z[W+6AĢJ= ,fgg- \^׶{yKmu?aE ̎{㗥< (o833)r4dX҃O=<䐀=p]']x jD'H ?Gx}׹'ja;n~ @6q]Ɯ ,,,0V*?(%wwvJcիpH[("8ȥq{okri|߰{}`ϛftˀ@\8w F/afhvHfleT@.-`Lu @S_0w Ȋ>'қHK>oQT xߟSܯ//|AK@ c]p*&Hmb'ɑܘv]S/>דN =-׃"87>K}dSm![n1́.W9|ߠ%L|: {ꩂg\UlB87y.~2Rϰ&C @Y9eVGx$d߮9g9^ 8~z/%_;./x`a0X%O v?,Q!,g":v\z( Xx |usxv}A®ݹw8F3z܂}(fwj#VKPL y؉ aYX E^xO;Ύ-uuwW+!g.횒 iቾQ81?qI'yvii&VWbs4P  C:gU *k_{1T{?}Tzg7y!= Ld\Jn߹~ؑ@*1H|[e"gϱ .a>p#2hdKaF4F$0 3CLavdwF881߿m䍾;'pRȥ7b/L! 8u:f *w`(1YL%F1|kYǿ9n x]~;-.ub9 ,2]d$:sݴ\ m1"uAcg )/_?Vo]or" ٤iߏHrW0DPP%~!f>FE}8U#!$0>68 ǔJn(JNAnuNIć&`(p (e Y.PB5TH_WvhjJGݮ tH`124>"@H"۲ByŴ=H;/w~淿{4+pJJ!Cw Yʾ'8@J rs!iy |2[$! |q>~lMm>Qߎ yfuuJmnBAqv#vgJ=wo[yA> Δ[OœxSmNO6=ܳk3[ E3~+l6kvO?3vR"<=5HAn1[;&gq vW@fE>OOhYf@SoNO 7wFvgRPM$Q0??dYۇiCQ(<( (mJ=om?5ؙ6JOӃ=kN=?YEO4 (޶vk(z OHu%#pSZU.7"Ftyc *nL W._HhA);ywp&<6BeFG+phEG޺wphvH(8{*9;wX-SfG]nݺňKKֳUo>%O̐KovR8ٱ>SeY!8uقVԙKpdyDK@vxs߹~ L^KPGN <5',):đh[0 IDATlB/nwTPT QEe>J-BPG %k Pyϧ_Z$'K@R!qnv#)Vzpr Q:i'%(N{z[B)ٽUpӾ>%:j`c QrbWWQ8֬/~ EUH#";Ћ0fFx~}Man"I$ KmFKO x8C@AC(d6#:bQ7n\﹂J'prAw>gJE/^K?1@39Q LS\8"-{)APb ӆjR!Mw|J''} k*Bzq0L`ff>%J v#DpzQZ0>:7w}J]58DbIښv{16:S)t),$I1> )n_@Wn?LT%D*&:=(=>+:A&E)?A,@G*BB/DG.AH*BJ,EJ/DL-FF2GJ1HK2IM3JI5MN6NK9NM9PO9RO<OQ8PQ9PU8TQ=VU=X[>YTA\VEYX@\YC]]C^ZE_\F`\F`YHb_Hd^Jg_Nh^NcaHgdKg`LibNjaPlbRmeQpfTriVtgXuiYwl[xm[zn\|n^|p^~p`rbtcsduexgwiyi{l}m~o~pwxqruuuvyz}xykd^^^^ckhOSYYYYYYYYYYSNg^SYYYYYYZYYYYYZYYYYX^SYYZYYZYZYYZZZZYZZZYYYXhSYYYZZYZYYZZYZZYZZYYZZYZZSf^YYYYZYYYYZYYYZYYZYYYZYYYZYYYXXYYZYZYZZYZZYZZYZZYZZZZZZYZYYZYSSYYZZYYYZYZZYZZZZZZZZZYZZZYZZYZZYSXYYYZYYYZYYYYZYYZYYZYZZZYZZYYYZYYYYS^YYYZZYYZYZZZZZZZZZZZZZZYZZZZZZYYZYYZXkYYZZZYZZZZZZYZYMC:8489BMYZZZYZZYZYYZZYfSYZZYYZZYZYZZJ4   ,?YYZZZZZYZZYS^YYYYYYZYZZZM# -8-% (YYYYZZZYYYYXYYYZYYZZZYY? CYYYZYY- YYZZZZYZZYZY^YYZYYZYYYZ9#YYYYYYYYZC CYZYYYZYYYZYXSYZYZZYZZZBYYYZZZZYZZY;:ZYZZZYZZYZZYlYZZYZYYZZSMYYZZYZZZZYYY ,YZYZZZZZYZYYgXYYYYYZYYY, 4YYYYYYYYYYYZYCYYZZZYZZZYYZXSYYZYZZYZYMYZYZZZZZZZZYZZ%9YZYZZZZZYZZYYYYZYZZYZY: YYZZZZYZZYZZZZYYYYZZZZYZZZZZYYnYYYYYYZYZ%8YYYYYZZZZZZZY##%(SYYYYZYYYZYYYkhYYZZYZZYZ?YZZZZZYZZYZYQYZ,8YYZZYZZZZZYYfcZYZYZZYYZGYZZYZZZZZZZZYYZZ:$ZYZZZZZYZZZZ]aYYYZYYYZYJYYZZZYYYYYYZZYYY(2YZYZYZZZYZYY]aYZZYYZZYZMYZZYZZZZZZZZY,#$MZYZZZZZYZZZZ]fYYZZYZZZY JYYZZZZYZZYZZY$GYYYZYZZYZZZZZYZ]iYZYYYYYYYCYZYYYZZZZZZYZ(CZYYYYZYZYYYZYZYfnZYZZZZZZZ- :YZZZZZYZZYZZY,#!#:ZYZZYZZZZZYZlYYZZYZZYZC,YZZYZZZZZZZZYCBBA:ZZZZZZZYZZZZSYYZZZZZZYYYZZZYYYYYYZZYYYYYYYYZYZZZYZYY^YYZYZZYZZ7BZZYZZZZZZZZYZ#YYZZZZZZZYZZYSlYZYZZZZZZY !YYZZZYZZYZZZQZZZYZZYZZZZZYiSYZYZZYZZZH 8ZYZZZZZZZZY4YZZZZZZZZYZZYaYYZYYZYYYY? ?YYZYZZYZYC#YYZYZYYYYZZY^YYYZZYZZZYZC8YYYZZZY?%YZZZZZZZZYZY`ZYZZZZZZZZYQ 4?CB8,YZZYZZYZZZY^SYZZYZZZYYYYYH% 6YYYZYZZZZYYmYYZZZZZZZZZZYYYC8%-6?QYYYZZYZZYZZYi`ZYYZZZYZYZZZZZYZZYYYYYYYYYZZZZZZZZZZ]XYZYYYZZZYZYYYZYYZZZYYZYYZYYZZYZYYYXXYZZZYZYZZZZZYZZYZZZZZZZZZZZZZZZZXYZZZZZZZZYZZZZZZZZYZZYZZZYZZYZZX`YYYYYZYZZZYZZYYYZZYZZYZZYYZY]mSYZZZZYZZZZZZZZYZZYZZZYZZYkaYYYZZZZYZZYZZZZZZZZZZY]aSYYYYZYYZZZYYZYYZY]m]QYYZZYZZZZYSXloifbbfio????( @7>%8>%:A&TT<XY?YVA\ZC\]A^ZE^]Da]GaYHb^He`JiaNj`PlcQmeRqfUrhVuiYwmZxmZzm]}o_|p^qatcsdvfvhyj}m~nqsuuvz}xx~QJD@@DJOQ@=>>>>>>>>>>>>>>>>>>>>>><J=>>>>>>>>>>>>>>>>>HD>>>>>>>>>>>>>>>>>>>>DD>>>>>>>>>>>>>>>>>>>>>>DJ>>>>>>>>>>>>==>>>>>>>>>>H>>>>>>>>6')6>>>>>>><>>>>>>=" &+& >>>>>>><>>>>>>= )>>>>>) 4>>>>>>>OD=>>>>= &>>>>>>>#->>>>>>><=>>>>>+ >>>>>>>>>#>>>>>>>>>>>>>> +>>>>>>>>>02>>>>>>>>QL>>>>>6 6>>>>>>>>##->>>>>>>>JF>>>>>0 >>>>>>>>>->5>>>>>>>FD>>>>>+ >>>>>>>>>>>4>>>>>>>@D>>>>>+ >>>>>>>>>!)>>>>>>>>@H>>>>>2 >>>>>>>>> >>>>>>>>>>FL>>>>>= >>>>>>>>>@>>>>>>>K>>>>>> 2>>>>>>>>>@6>>>>>>>>Q=>>>>>2 !>>>>>>>>2>>>>>>>>>D>>>>>> 2>>>>>>> >>>>>>>>@>>>>>>> 2>>>>>- >>>>>>>>Q@>>>>>>># !14- >>>>>>>@>>>>>>>>6# )>>>>>>>K>>>>>>>>>>>444<>>>>>>>>>KF>>>>>>>>>>>>>>>>>>>>>>FF>>>>>>>>>>>>>>>>>>>>FK>>>>>>>>>>>>>>>>>>K@>>>>>>>>>>>>>>@F<>>>>>>>>>CNJFDHM??( BJ,EL.FJ0GL0IK3IN2LO4NP6UU<[WB^ZD`ZGc]Ig_Ld`Ig`LjdNkcPnfRqgUphTvmXxn[{n^{p]}p_~q`sbxgxh}m~opqrtvyy}3,((,46&&&&&&&&40&&&''''&&&06&&'' &&&4&&''&'&6&&'"&&' ''&4,(''&'& &'',+&' '''&"&''(+&' &'''#'''(0''''''&'',4''#''&& '&''6('(  '&'&7'''' &''82'''''('&''17(''''''&86/(+/6( jeakfbida5hdaPfb`bca_xdb_eb`fb`fc`gc`fc`fb`eb`da_}db`hidaThcakfbkebida1da_pa_^gb`uketk{puxyyyyyyyyyyyyyyyyyyxu}qvlxngjdaa_^da_~ida@lfb kfbhdaB*;@'=B)>C*>C*>C*>C*=B);A(DG0QP:^YEldQ}p_|mxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysc`^ohdfc`StkexyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxwhndSUR=?C+D*CF.XU@pfTwhxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyulc`_lfcda_ٖuyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvgd]JCE/:@'9@&;B'=D(>E)?F)?G*@G*?G*?F*>F)>E(=D(E)>F)>F)>F)>E)=D(F)=D(;B':@&8?%7>$7=$6=$6=$7=$7>$8?%:A&;B'=D(>F)?G*@H+@H*@H*@G*?F)=E(%;A'=D(?F)@H+AI+BJ+BJ,BJ,AI+@H*>E)F)@H*AI+BJ,BJ+AI+@H+?G*=D(:@&TR=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzngfc`]b`^{pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvjZBC.7>%;B'>E)@G*AI+BJ+CK,CK,CK,BJ+AH+>F)78'/3.428!6<#9@&=D(?G*AI+BJ,CK,CK,BJ,AI+>E)F)AH+BJ+CK,CK-CK,AH+%%4:#NK9ufyyyyyyyyyyyyyyyy}oZSD.2/44;#9@&=E)@H*BJ+CK,CK,BI+>E)KL5yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywfb`mgcfc`=skexyyyyyyyyyyyyyyyyyyyyyyyyyyyyxg_N7<$9@%=D(@H*BJ+CK,CK,DL-DL-CK-CK,AI+?F);B'6=$<>)vjZxyyyyyyyyyyyyyyyyyyyqc;;*-239"8?%=D(@H*BJ+CK,BJ,?G*=C)}nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytkda_sa_^}qyyyyyyyyyyyyyyyyyyyyyyyyyyyyw]VE5;$:@&>E)AH+BJ,CK,CK-DL-DL-CK-CK,AI+?F*;B'6<$KJ7}nyyyyyyyyyyyyyyyyyyyyyyrJG7-228!8?%=D(AH+BJ,BJ,@H*;C'wkZyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvda_pid ida,nhcxyyyyyyyyyyyyyyyyyyyyyyyyyyywZTC6;$:A&>F)AI+BJ,CK,DL-DL-DL-DL-CK,BJ+?G*;B'6=$UQ>tyyyyyyyyyyyyyyyyyyyyyyyyvOJ;-239"9@%>E)AI+BJ,AI+=D(_ZFyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}qifc`_b`_yoyyyyyyyyyyyyyyyyyyyyyyyyyyyx^WF5;#:A&>F)AI+BJ,CK,DL-DL-DL-DL-CK,BJ+@G*wyyyyyyyyyyyyyyyyyyyyyyyyyyvLH8-24:":A&?F)AI+AI+>E)JK4yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyta_^pidkfbgb`䚅wyyyyyyyyyyyyyyyyyyyyyyyyyyyj`P4:#9@&>E)AI+BJ,CK,DL-DL-DL-DL-CK-BJ,AH+=D(8>%PN:vyyyyyyyyyyyyyyyyyyyyyyyyyyyyr?>-.45<#F)9@&CD/qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvh03!16 8>%=E)@H*?G*&ufyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyh^O-23:":A&?F)?F);B'rhVyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywfb`ohd jealfbxyyyyyyyyyyyyyyyyyyyyyyyyyxSO>4:":A'?G*BJ+CK,DL-DL-DL-DL-DL-DL-CK,AI+=E)8?%e]Lyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyw?>./47=$=D(>F)E)AI+CK,DL-DL-DL-DL-DL-DL-CK,BJ,?G*;B'DE/tyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}n`,139!:A&=D(F)BJ+CK,DL-DL-DL-DL-DL-DL-CK-BJ,@G*;B'HH3wyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}o`,117 6=$7>$8=&yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxmgkfb?gcaGzohyyyyyyyyyyyyyyyyyyyyyyyyyu;=)5;#E)9?%uiXyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx>=-,117 28!49$qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxneb`~da_yoyyyyyyyyyyyyyyyyyyyyyyyyyqfV17!9?%>F)BJ+CK,DL-DL-DL-DL-DL-DL-DL-CK,@H+?=.NI:rdyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxjdaohdjeaid`ꛆxyyyyyyyyyyyyyyyyyyyyyyyywi16!7=$=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-CK,BI+>E):@'ufyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxmglfc=hda5vlfyyyyyyyyyyyyyyyyyyyyyyyyyg]M17 9@&?F)BJ+CK,DL-DL-DL-DL-DL-DL-DL-CK,AI+$>E)BI+CK,DL-DL-DL-DL-DL-DL-DL-CK,BJ+>E)D*?D+>E)AH+JM3{n^yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxgc`pid c`^ؙwyyyyyyyyyyyyyyyyyyyyyyyyc[K16 9@%?F)BJ+CK-DL-DL-DL-DL-DL-DL-DL-CK,AI+=D(CF/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyse;@(?F*BJ,[XCteqwikbQ@F,DL-HO0ym\yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyphcqid"jeaid`꜇yyyyyyyyyyyyyyyyyyyyyyyyyMJ928!:A&?G*BJ,DL-DL-DL-DL-DL-DL-DL-DL-CK,AH+yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytf9=&;B'TR=xyyyythXCJ-IQ0MR5~pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxngjeb>,39";B'@H*CK,DL-DL-DL-DL-DL-DL-DL-DL-CK,@H+;B'f_Lyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvh7:&8<&pbyyyyyxHJ2HQ0LU2qiUyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyrjkfbThda4vmfyyyyyyyyyyyyyyyyyyyyyyyyx/34;"C*yiyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyXW?LU2MV3WX>yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|qfc`jeb`ulyyyyyyyyyyyyyyyyyyyyyyyyqb-26=$=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-DL-BJ,?F)>C*}nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{lIP2LU2JS1jcOyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rgcajebgulyyyyyyyyyyyyyyyyyyyyyyyyqb-26<$=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-DL-BJ,?F)=B)ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxsyyyyzkST;IR1IR1GM0zkyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysgcajfbavmyyyyyyyyyyyyyyyyyyyyyyyypb,26<#=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-DL-BJ,?F)=B)tyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyrSPD*xhyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyte@F+DL-~p`yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxneb`yjeb.ulfyyyyyyyyyyyyyyyyyyyyyyyyy?>-17 9@&?F*BJ,DL-DL-DL-DL-DL-DL-DL-DL-CK,@G*:A'ueyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyug@F,DL-|n^yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvljebclfbohcyyyyyyyyyyyyyyyyyyyyyyyyyNJ:058?%>E)BJ+CK,DL-DL-DL-DL-DL-DL-DL-CK,@H+;B'zm]yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvhAF,FN.|o^yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}qikfbPlfb gc`曆xyyyyyyyyyyyyyyyyyyyyyyyycZK.36=$=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-CK,AH+E)NO8yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{m;@(BJ,HP0KT2KT2JS1IR1JR1JS1HQ0FM0yjyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxfb_pidda_zoyyyyyyyyyyyyyyyyyyyyyyyyyA@/058?%>E)BJ+CK,DL-DL-DL-DL-DL-DL-DL-BJ,?G*>D*uyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}p6:$;B'@H*CK,DL-DL-CK-DL-DL-CK-AH+tdyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyua_^gcaXsjyyyyyyyyyyyyyyyyyyyyyyyyycZK-26<#=,/57>$=E)AI+CK,DL-DL-DL-DL-DL-DL-CK,BJ+>E)RQ;yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyulflfc2b_^syyyyyyyyyyyyyyyyyyyyyyyyyk`Q+14:";B'@G*BJ,CK-DL-DL-DL-DL-DL-CK-BJ,@G*?D*~oyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxgb`pid eb`wwmyyyyyyyyyyyyyyyyyyyyyyyyyr14"06 8>%>E)AI+CK,DL-DL-DL-DL-DL-DL-CK,AH+F)LM5xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywi78&59$9<'kbQyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvlfc`qlfb fb_ᚅwyyyyyyyyyyyyyyyyyyyyyyyyy}p14"057>$=D(AI+CK,CK-DL-DL-DL-DL-CK-BJ,@H*>D)tcyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyg]N055;#6<#6;$uyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysjemgc1b`^}qyyyyyyyyyyyyyyyyyyyyyyyyyyf\M,039":A&?F)BJ+CK,DL-DL-DL-DL-DL-CK,AI+>E)US=xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyCB04:"9@&9@&=A*yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvc`^qiegc`S|piyyyyyyyyyyyyyyyyyyyyyyyyyyt;;*.35<#D)sbyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytf38"8>%%=D(AH+BJ,CK,DL-DL-DL-DL-CK,BI+>F)NO8wyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyYSB4:":A&=D(;B'US>yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvlflfc=b`^|qyyyyyyyyyyyyyyyyyyyyyyyyyyy]UF+028!9@%>E)AI+BJ,CK,DL-DL-DL-CK-BJ,AH+>D)ofSyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|n6:%8>$=D(>F);B'a[Hyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvc`^qiehdaFxmfyyyyyyyyyyyyyyyyyyyyyyyyyyyvEB2,13:":@&>F)AI+CK,CK,DL-DL-DL-CK,BJ+?G*@F+xhyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy[UD4;";B'?G*?G);B'jbOyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywmgca{mgcc`^՗vyyyyyyyyyyyyyyyyyyyyyyyyyyy}o88'-34:#:A&>F)AI+BJ,CK,DL-DL-DL-CK,AI+>F)JL4ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyrc5:$9?%>E)AH*?G*:A'rhVyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxngblgc%eb`tulyyyyyyyyyyyyyyyyyyyyyyyyyyyyug34#.34:#:A&>E)AI+BJ,CK,DL-DL-CK,BJ,AI+>E)UT=uyyyyyyyyyyyyyyyyyyyyyyyyyyyrDD07=$=D(@H*BI+?G*:A&{n^yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~ra_^lfcidaꚅxyyyyyyyyyyyyyyyyyyyyyyyyyyyyqc03!.34:":@&>E)@H+BJ+CK,CK-DL-CK,BJ,AH+=E)XU?tyyyyyyyyyyyyyyyyyyyyyyyyywRO=6<$E)SR;pyyyyyyyyyyyyyyyyyyyyyyyvXSA6=$;B'?F*BI+CK,BJ,?G*F)AH+BJ+CK,CK,CK,BJ+AH+>E)GJ1tcyyyyyyyyyyyyyyyyyyyyyrSP=7=$;B'?F)AI+CK,CK-BJ,?F)>C*yjyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzohgcaZa`^zoyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~pJF6,0055;#9@&=D(?G*AI+BJ+CK,CK,BJ,AI+?F)>E*d^JqyyyyyyyyyyyyyyyyyxpaEF08?%F)=C)~pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyub`^qidjeb'keaxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywg]N.2 -228!6=$:A&=D(?G*AH+BJ+BJ,BJ,AI+@G*=E)CH.ldQpyyyyyyyyyyyyyxteUR>9>&:A&=E)@H*BJ+CK,CK,DL-CK,BJ+>E);A(vyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyngidaXb`_vmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywiFC3,0.439"7=$:A&=D(?F)@H*AI+BI+AI+AH+?G*=D(?E+YV@wlZ{lxyyyyyyttdjbPII3:@':A'=D(?G*AI+BJ+CK,CK,CK,BJ,BJ+@H*%yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysc`_pidlfcfb_☃vyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxuhY<;+,1/439!6<$9@%;B'=E(?F)@G*@H*@H+@G*?F)=E)D*AE,OO9TS'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxqidjeb?da_i{phyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvreW>=--1.317 4:"7=$9@&;B'E)>F)?F)>F)>F)>E)=D(=D(E)?F)?G*@G*@G*@G*?G*>F)>E)=D(;B':A&9?%6=$39!8:'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyob`^ngcb`^~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx~oaRM=14",1.417 3:"6<#7>%9@&:A&;B'$6<#4:"28!16 05 /3 =<,yyyyyyyyyyyyyyyyyyyyyyyyyyyyyvhc`oidida2mfbwyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyystgYSM=46%.2-2/417 39!4:"5;#6=$7=$7>%8?%8?%8?%8?%8?%8?%8?%7>%7=$6=$5;#4;"39"28!06 05 15!>>,SN=i_P}n`}oyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyogeb`eda_~rjyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxtflaRTN>>>-.204!-2-3.4/50506 06 06 06 05/5/4.315!15!46$DC1WQAi_P~o`qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{pa_^rjeohcb_^}qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywvh~o`reWg]NcZJ^VGZSCWPAZSD_WHc[Ji_OrfW|n_se~pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvgb`ngc jeb'hc`痃vyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxsjehdaTfb`Xsjexyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyskca_b`_~riyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzob_^rje pida_^wmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysda_qjeohcc`^ȍ|qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuhd`mgc.lfc da_ؐ~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvlfbjebEkfb,fb_ߒsyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvogcgcaTlfb1gc`syyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvohcgca\lfc2fb_ߑ~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvngbgca\kfb,da_׍|qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytkfagcaRlgc c`^LJwmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rgc`kfc?ohda_^~qixyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyync`^lgc*qieb`_sjevyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx}qia_^qiefc`Yhc`}qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytphcdb`skemgc(b`^~rjwyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxnea_jebFohdda_}lfb~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuukeb`_ohdjeb3b`^|phvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvlda_hdaSqjdqidgc`gfb_wmxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx{plfbc`_pidngcdb`keayoxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx~rrjdc`_mgc,kfb&b`^lfbynxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx}qskeb`^ieb>kfb&a_^idaulvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywynngca_^ieb>nhcfb`tc`^wmg|qxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxs}qigb`ca_mgc+ohdiebGb`_hc`|pi}qwyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxstklfbb`^gcaYpidohdhdaRb`_eb_vlfwmsxyyyyyyyyyyyyyyyyyyyyyyyyyyxuyozohic`b`^hcacnhcqid kfb9fc`ub`^gb_sjesjyosvxyyyyyyyyyyyyyyywt{pukvlfid`a_^eb`kfbGqjepid mgc-idaXeb`a_^b`^gc`ogbulfyoh}qisjulvlvmultk~ri{ohvmfphcid`c`^`_^fb`hdaalfc6ohdngc pidkfb/jebCkfbPlfcYlgc_mgcbmgccmgc`lgc[lfcTkfbFlfc3qie#ohd?????????????(` ida hc`&hcaGgc`ghd`fb`idakealfblfbkebidafb`gc`fc`midaMida,jeahc`hdagc`Pfb`jeaske~rjyn~ruvwxxxxwvu~ryotkulfkebgc`gc`Yida idahda$eb`ridaxngyouxxxyxxyxxyxxyxxyxxxxv{p|pikebfb`hda1jeaidafb``ida|qi~rxyyyyyyyyyyyyyyyyyyyyyyyyyyxtskkebgc`qjebhdaeb`rje{pwyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyx}rvmfgc`jeb-idaea_wmgsxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxxu|qieb`ida,jeafb`|ulf쓀tyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyv{phfb`lfbidahdaKmgb֎}qxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxtrjegc`elfbkebeb_tkxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxxynhc`lfb!hda>mgcؒsyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvskegcaYlfbkebfb`ozphwyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxxulgc`ngclfbfb`wnxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx|qhc`mgckfbhca}qyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxtlfbmgc#kfbidaÒsxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyvngclfb)kfbjdaƔtxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxvohclfb)kfbida”tyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvnhcmgc"kfbhcasxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyvlfbngcjeaeb_|qxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxtgc`nhcfc`gvmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|qgc`mgbjeb5xngxxxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyulhcaYkeb kfbЙwyxxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxu~o}n~pvxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyxskemgc!eb_~syyyyyyyyyyyyyyyyyyyyyyyyyyyyyw|n{n^haNZVBNN7DG/;A(C*JK4WU?f_Kxm[zkvyyyyyyyyyyyyyyyyyyyyyyyyyyyvhc`mgchda=~rjyxyxxxyxxyxxyxxyxxyxxyxyxxyu~p`_ZFFH1;A';B'E)>E)>E)=D(=D(F)@G*AH+AI+@H*?F)=D(;B'9@&8?%7>$7>$8>%9@%:A&E)?G*@G*@G*?F)=E(>D)IK3d^Jtexyxxyxxyxxyxxyxxyxxyxxxrjemgcfb`hzoyyyyyyyyyyyyyyyyyyyyyyyyzkXT@:?';B'>E)@H*BI+BJ+BJ+AI+?F)E)@H+BJ+BJ+AI+@H*=E(F)AI+BJ,CK,CK,BJ+@H*=D(8>%5:#HG4j`PufsxxytxjodULI804!17 6=$;B'?G*BJ+CK,CK,BJ+=D(kcPyxxyxxyxxyxxyxxyxxyxxyx{phjeb,ea_z}rxxyxyxxxyxxyxxyxxyxxyxwhEF19?%>E)AH+BJ,CK,CK,CK,BJ+?G*;B'6<#OL9texxxyxxyxxyxykVP@.339!9@&?F)AI+CK,CK,@G*KM5yxxyxxyxxyxxyxxyxxyxxyxvfb`idarjd䜇xyyyyyyyyyyyyyyyyyyyyx{m^:?(:A'?G*BJ+CK,CK-DL-CK,BJ+?F)9@&=@*wjZxyyyyyyyyyyyyyy~oa8:(16 8?%>F)BI+CK,AI+=C)}nyyyyyyyyyyyyyyyyyyyyyyy}qijeb.fb`n|qyxxyxyxxxyxxyxxyxxyxxthX8=&;B'@H*BJ,CK,CK,CK,CK,BJ+?F*9@&GG2zlyxxyxxyxxyxxyxxxy|nA@.06 9?%?F)BJ+BJ+=D(wkZxxyxxyxxyxxyxxyxxyxxyxxugc`idamgbԛxyxxyxyxxxyxxyxxyxxyxxk[7<%;B'@H*BJ,CK,DL-CK,CK,BJ,@G*:A&HH3qxyxxyxxyxxyxxyxxxyx~p>>,17 :A&@G*BI+>F)a\GxxyxxyxxyxxyxxyxxyxxyxxxvmglfbgcaGvmyyyyyyyyyyyyyyyyyyyysd8<&;B'@H*BJ,CK,DL-DL-DL-CK,AH+;C'CE/~pyyyyyyyyyyyyyyyyyyyyyyk57%3:"E);@(ufyxxyxxyxxyxxyxxyxxxyxxytgY.37=$>E)?G*AE,txyxxyxxyxxyxxyxxyxxyxxyxkeblfbidaulf휇yyyyyyyyyyyyyyyyyyyxSO=6=$>E)BJ+CK,DL-DL-DL-DL-CK,@G*:@&jbPyyyyyyyyyyyyyyyyyyyyyyyyxMI917 :A&>F)%8?%pfTxyxxyxxyxxyxxyxxyxxyxxyxxkebmfbidaqidyyyyyyyyyyyyyyyyyyyqfV3:"%?G*CJ,CK,DL-CK,CK,DL-CK,AH+;B'shWyxxyxxyxxyxxyxxyxxyxxxyxxyxxxDA2(-,0k`Qxyxxyxxyxxyxxyxxyxxyxxyxx{pidaYfb`g}ryxxyxxyxyxxxyxxyxxrc28!;B'AI+CK,CK,DL-CK,CK,DL-CK,?G*=B)qyxxyxxyxxyxxyxxyxxyxxxyxxyxxy~pugYsexxyxxyxxyxxyxxyxxyxxyxxyxxvhcagc`vyyyyyyyyyyyyyyyyyy]VE5;#=E)BJ,CK-DL-DL-DL-DL-CK-BJ,=E)VS>yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxlfblfbgc`lfb˛xyxxyxxyxyxxxyxxyxx<=*8>%?G*CK,CK,CK,DL-CK,CK,CK,AI+F)QP:xxyxxyxxyxxyxxyxxyxxyxxxyxyk8=&CF/ryxxneSFN.LS3}nxxyxxyxxyxxyxxyxxyxxyxxy~rhdamhcaZ|qxyxxyxxyxyxxxyxxyxOK:5;#>E)BJ,DL-CK,CK,DL-CK,CK,BJ,>E)_ZFxxyxxyxxyxxyxxyxxyxxyxxxyxrYSBthXxyxx~pDK.LU2skVxxyxxyxxyxxyxxyxxyxxyxxyuidafc`o~syyyyyyyyyyyyyyyyyyCB06=$?F)CK,DL-DL-DL-DL-DL-DL-BJ+=D(ldQyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvJN3LU3ebJyyyyyyyyyyyyyyyyyyyyyyyyvgc`hca~txyxxyxxyxyxxxyxxyx;<*7=$?G*CK,DL-CK,CK,DL-CK,CK,BJ+$?G*CK,DL-CK,CK,DL-CK,CK,BI+$?G*CK,DL-DL-DL-DL-DL-DL-BI+~p`xyxxyxxyxxyxxyxxyxxyxxyxxyxlfbhda{tyyyyyyyyyyyyyyyyyyA@/5;#>E)BJ,DL-DL-DL-DL-DL-DL-BJ+E)neSxxyxxyxxyxxyxxyxxyxxyxxxyxxvjZBI,ngRyxxyxxyxxyxxyxxyxxyxxyxxyxxyxxytjebhdaE)BJ,CK,CK,DL-CK,CK,CK,@H*CG.xxyxxyxxyxxyxxyxxyxxyxxxyxxzl];B'CK-GP/HP0GO/GP0GO/EL.zkxxyxxyxxyxxyxxyxxyxxyxxyrjmgc&kebśxyxxyxxyxyxxxyxxyxxPL;28!D){lxyxxyxxyxxyxxyxxyxxyxxxyxxwiXTA\XD`[Gb]Ic^Jf_LXU@:A'vgxxyxxyxxyxxyxxyxxyxxyxxyskengc fb`vyyyyyyyyyyyyyyyyyytgY.38?%@G*CK,DL-DL-DL-DL-CK-BJ,>E)ukYyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuoatyyyyyyyyyyyyyyyyyyyyyyyxjeagc`^|qyxxyxxyxyxxxyxxyxxt47$4:"=D(BJ+CK,DL-CK,CK,DL-CK,?G*YWAxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyxxugc`ida(~rjyxxyxxyxyxxxyxxyxxy[TE/59@&@H*CK,DL-CK,CK,DL-CK,AI+AF,ryxxyxxyxxyxxyxxyxxyxxxyxxyxxsrxyxxyxxyxxyxxyxxyxxyxxyxxyxxyokebPidangcלyyyyyyyyyyyyyyyyyyy{m03!5;#=D(BI+CK,DL-DL-DL-CK-BJ,>E)rhVyyyyyyyyyyyyyyyyyyyyyyyyyyyxj47$5:$UR?xyyyyyyyyyyyyyyyyyyyyyyyyyyxngmgceb_vxxyxxyxyxxxyxxyxxyx[TD/49?%?G*BJ,CK,CK,DL-CK,CK,@H*KM5vxxyxxyxxyxxyxxyxxyxxxyxxyxg]M39!8>%8=&wxxyxxyxxyxxyxxyxxyxxyxxyxxidahcaHwnxxyxxyxyxxxyxxyxxyxq46$39!;B'AH+CK,CK,DL-CK,CK,BJ+>E)wlZxxyxxyxxyxxyxxyxxyxxxyxxywBC/8>%;B'FH1yxxyxxyxxyxxyxxyxxyxxyxxyx~rhcarkeb qid囆xyyyyyyyyyyyyyyyyyyysgX-25<#=D(AI+CK,DL-DL-DL-CK,@H*GJ1tyyyyyyyyyyyyyyyyyyyyyyyy}o`4:#;C'=D(PO9yyyyyyyyyyyyyyyyyyyyyyyyyy|qimgc%fb`uxyxxyxyxxxyxxyxxyxxxUO?.37>$>E)AI+CK,CK,DL-CK,BJ,?F)b]HxyxxyxxyxxyxxyxxyxxxyxxxMK88?%>F)=E([WCyxxyxxyxxyxxyxxyxxyxxyxxywjeaida7tkxyxxyxyxxxyxxyxxyxxytA@//58?%>E)BI+CK,CK-CK,CK,AI+?F*ym[yxxyxxyxxyxxyxxyxxxyxxwj[6;#=D(@H*=E(b]Iyxxyxxyxxyxxyxxyxxyxxyxxy{pida_jebidaĚxyyyyyyyyyyyyyyyyyyyy}o::)05 8?%>E)AI+CK,CK,CK-CK,AH+BG,tdyyyyyyyyyyyyyyyyyyyy|n=@*;A'@H*AI+=D(kcQyyyyyyyyyyyyyyyyyyyyyyyyxrjenhcgcaZyoyxxyxyxxxyxxyxxyxxyxxzl:;*/57>$=D(AH+BJ,CK,CK,BJ,@H*BH-rbxyxxyxxyxxyxxyxxxyqHH49@&?G*BJ+BJ+-238!9?%=D(@H*BJ+BJ,BJ,AI+>F)SSF)@G*@H*@H*?F)=E)AF,QQ:a\Gg`LiaNd^JYVAHJ3=B);B'=E)@G*AI+BJ+BJ+AI+@H+?G*=D(8>%ueyxxyxxyxxyxxyxxyxxyxxy}qgc`|kebgc`vyyyyyyyyyyyyyyyyyyyyyyyyy{m\TE46$.428!6<#9?%;B'=D(>E)>F)>E)=E(=D(E)?F)?G*?G*?F)>E)=D(;B':@&8>%4;"16!ufyyyyyyyyyyyyyyyyyyyyyxmgcphd jeb)wmgxxyxxxyxxyxxyxxyxxyxxyxyxxysqeVKG715".417 4:"6<#8>%9@&:A&:A';B';B';B';B';B':A':A&9@&8?%7=$5;#39"27!27">?,QMg^N~o`~pxyxxyxxyxxyxxyxxyxxyxxyxtfb`nhclfcgc`uyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx|nsexk\qeVmbSh^Oj`QodTsgW{m^uf{mxyyyyyyyyyyyyyyyyyyyyyyyyyyyywngcohdkfb qid⚅xxxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxx{phlfb>hdaI|qixxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxwnhcaofc`owmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}qgc`pidmgcfb`{pxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxysidapidmgcfb`}qyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxtidaohdnhcgc`}qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytjeaohdnhcfc`{pxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxysidaohdmgcfb`wmyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxx|qidaohdmgcfc`o}qixyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvmfb`pididaIqid㖂uyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxwxnghdagohdlfb hdazpxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyx~rlfbmgc4mgcfc`swmgvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyw~qjfb`ohd lfb)hc`vmxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxzokebkfb=ngcidaPlfbӊzoxxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxx~rqidhdaiohcmgcgcadmgb׉yoxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx|qrjefc`zohdlfc hda[jeaĀskuxxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyvvmmgbhdammgcmgckeb7fc`qidwnvyxxyxxyxxyxxyxxyxxyxxxyxxywzoulfgc`jebFohcngc jebGgc`ngc~rj|qvxyyyyyyyyyyyyyyyyxv}rtkqidgc`jebUnhcmgclfc(ida^gc`kebske}qiwm{p~rsuvvutr|qwn~rjulflfbhc`idahlfb1ohcmgc mgc!jeb=jebVhdaljea{keblfblfbkfbjeb~hdankfbYkfbAmgc&ngc mgc????????(@ Bda_ea_$fb`Sjea}qidulfyoh{pi|qiyohulgrjelfbgc`Yeb`*eb`eb`fb`Yqid}rj{puxyyyyyyyyxv|qskskehcadfb`da_fb`>pidumvyyyyyyyyyyyyyyyyyyvwnrjegc`Ifc`da_gc`KulfǏ~rxyyyyyyyyyyyyyyyyyyyyyyytyohhdaZgc`gc`/skesyyyyyyyyyyyyyyyyyyyyyyyyyyyyuvmgida>fc`kfbzoxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|qmgcidahda&vlg˗vyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywzohjeb5idaHul훆xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxnkeb^idab{pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rlfb|jeak}ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytlfbida`}ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytlfb|idaDzpyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rjeb^ida"tk꜇yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxnjeb5fb`tkfśxyyyyyyyyyyyyyyyyyyyyyyywrrwyyyyyyyyyyyyyyyyyyyyyyzphjebjeb|vyyyyyyyyyyyyyyyyyyt~q`haNWU?JL4@E+C)=D(?G*@H*?G*=D(;B'9@&9@&:@&;B'=D(?F)?F*>E)FJ1`\F{o^vyyyyyyyyyyyyyy|qjea>qidxyyyyyyyyyyyyyyxzm]GI2)PM:ZTCZTCPM;;=)28!7>$=D(AI+BJ+AI+=E)neRyyyyyyyyyyyyyyyvmghcafc`=}ryyyyyyyyyyyyyyqTQ=;B'@H*BJ,CK,BJ,?G*9?%OM9qbwyyyyxteRN<17!:@&@H*CK,BJ+LN5yyyyyyyyyyyyyyyuidaZrjexyyyyyyyyyyyyyzkCE/=D(BJ+CK,CK-BJ,>F)=A)wk[xyyyyyyyyy{m^69%8?%@H*BJ,>E)}nyyyyyyyyyyyyyyyxnhhdafb`.{qyyyyyyyyyyyyyzk?C,>E)BJ,CK,DL-CK,?G*?C+xhyyyyyyyyyyyyvh69%9@&AI+?G*xm[yyyyyyyyyyyyyyytidaImgcxyyyyyyyyyyyyrCE/=E(BJ,CK,DL-CK,AH+;B'BJ+CK,DL-DL-BJ,E)TS;B'BJ,DL-DL-DL-CK,?G*h`Myyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|qida*eb_;syyyyyyyyyyyv8;&>E)CK,DL-DL-DL-CK,=D(ueyyyyyyyyyyyyyyyyyvvfyn[|o^pyyyyyyyyyyyyyyyyyvjeaYgc`cwyyyyyyyyyyyuf4:"@H*CK,DL-DL-DL-BJ,?D*vyyyyyyyyyyyyyyyyqBF-IN2f`K\XCDJ.vlXyyyyyyyyyyyyyyyyxngclfbyyyyyyyyyyyyqeV7=$AI+DL-DL-DL-DL-BJ+MN6yyyyyyyyyyyyyyyyy~p8=&tdyyfaLJR2}nyyyyyyyyyyyyyyyyskepidyyyyyyyyyyyybZJ8?%BJ+DL-DL-DL-DL-AI+YWAyyyyyyyyyyyyyyyyyx~pyyy}p`JS1yo[yyyyyyyyyyyyyyyyvmgtlfyyyyyyyyyyyyZTC9@&BJ,DL-DL-DL-DL-AH+b]Hyyyyyyyyyyyyyyyyyyyyyy}q_LU2wnYyyyyyyyyyyyyyyyyzohwmgyyyyyyyyyyyyVQ@9@&BJ,DL-DL-DL-DL-@H*gaMyyyyyyyyyyyyyyyyyyxwyu\\BJR1zkyyyyyyyyyyyyyyyy|qjwmgyyyyyyyyyyyyWRA9@&BJ,DL-DL-DL-DL-@H*ibNyyyyyyyyyyyyyyyyyyhaNGM1PT8GL0DJ-pgTyyyyyyyyyyyyyyyyy|qitkfyyyyyyyyyyyy^WF8>%BI+DL-DL-DL-DL-AH+g`Myyyyyyyyyyyyyyyyyya\H`^Fue~q`~pyyyyyyyyyyyyyyyyyyyohpidyyyyyyyyyyyyi_P6<#AI+DL-DL-DL-DL-AI+a\Gyyyyyyyyyyyyyyyyyyb]I`^Fyyyyyyyyyyyyyyyyyyyyywmglfbxyyyyyyyyyyyzl]39!@G*CK,DL-DL-DL-BJ+XV?yyyyyyyyyyyyyyyyyyd_Ka_Fyyyyyyyyyyyyyyyyyyyyyskegc`_wyyyyyyyyyyy}o06 =E)CK,DL-DL-DL-BJ,JM4yyyyyyyyyyyyyyyyyye^KKQ4Z\?VY=UX;QT9tyyyyyyyyyyyyyyyxmgb}eb`6~ryyyyyyyyyyyy@@.:A&BJ,DL-DL-DL-CK,?E*syyyyyyyyyyyyyyyyyqfVRQ;WW?YX@[YBAG,|nyyyyyyyyyyyyyyyuidaTeb` vmyyyyyyyyyyyycZJ5;#@H*CK,DL-DL-CK,?G*~q_yyyyyyyyyyyyyyyyyyyyyy{mwyyyyyyyyyyyyyyy{pida$xnhʜyyyyyyyyyyyy{m05 6<#@H*CK,DL-DL-CK,BH,syyyyyyyyyyyyyyyyyxj48#DE0xyyyyyyyyyyyyyyyyysjefb`<~syyyyyyyyyyyy{m26":A&BI+CK,DL-CK,@H*ldPyyyyyyyyyyyyyyyyye]L8?%@D+yyyyyyyyyyyyyyyyyvjeaYfb`|qjᜇyyyyyyyyyyyyyi^O17!=D(BJ,CK,DL-CK,CH-~oyyyyyyyyyyyyyyyv?A+=D(KM5yyyyyyyyyyyyyyyyyumjeblfbxyyyyyyyyyyyyxNJ94:">E)BJ,CK,CK,AI+TTF)BJ,@H+b]Iyyyyyyyyyyyyyyyyvmghdagc`0{pyyyyyyyyyyyyyyv^VF05 7>%>E)AI+BJ,AI+HL2wlZvyyyyyysjbO=C)?G*BJ,CK,@H*ibNyyyyyyyyyyyyyyysjebKnhcxyyyyyyyyyyyyyyyrcCB017 8>%=D(@G*AH+?F*EI/_[FrhUym\wlZmdQXV@?D*>E)AI+BJ+BJ+AH+E)>E)=D(;B'8?%5<#27!kaQyyyyyyyyyyyyyyzolfb/jeahuyyyyyyyyyyyyyyyyyy}oodUSN=<>+16!17!39"4:"4;"4:"39"39"38"@A-RNE)E)GK1_[Eym[txyyxyyxyyxvlfbYZWVskћxxxxxxxxxxx{lVT>=D(AI+BJ,@H*:@&GH3`ZHkaPg_NVR?:=(8?%?G*BJ+@H+]ZDxxxxxxxxxxxvmda_ ida\wyxyyxyyyxxvjZ>C*AH+CK,CK,>F)IJ4sdxxyyxuh^N5:$>E)BJ,BH-vyyxyyxyyxyxngcsRPOskϛxxxxxxxxxxpeTB+AI+CK,DL-CK,?E+}nyxyyxyyxyyxxNK9;B'`\FyyxyyxyyxyyxyohPNLumxxxxxxxxx_YG=E(CK,CK,CK,AH+f_Kxxxxxxxxxxxxxtf17!ON9xxxxxxxxxxxxyob_]b_^&txyyxyyxys:?'AI+DL-CK,CK,>E)}nyyxyyxyyxyyxyx\UEmbSyyxyyxyyxyyxvgc`;gc`ZxxyyxyyxythX:A&CK,DL-CK,CJ,PQ9xyyxyyxyyxyyxywvxyyxyyxyyxyyxxohcqqidxxxxxxxxxYTA>E)CK,CK,CK,AI+haMxxxxxxxxxxxxtTS(AI+CK,DL-CK,?G*vfxyyxyyxyyxyyxyyyqiUX[>yxyyxyyxyyxytlрskěxxxxxxxxx6;$AI+CK,CK,CK,?F){lxxxxxxxxxxxxxxxw]]CcaHxxxxxxxxxxxxvmڀskœyxyyxyyxy6;%AI+CK,DL-CK,?F)}mxyyxyyxyyxyyx]ZDQT9IM3WW>~oyxyyxyyxyyxyvm|qjyxyyxyyxy>@+@G*CK,DL-CK,?G*zjxyyxyyxyyxyyxZYAxgwxyyxyyxyyxyyxytlxnhxxxxxxxxxKI6>E)CK,CK,CK,@H*tcxxxxxxxxxxxxx\ZCwexxxxxxxxxxxxxxx~rjqidyxyyxyyxy`XH;B'CK,DL-CK,AI+sjWxyyxyyxyyxyyx\XCPU8RV9PU8ogRyxyyxyyxyyxyxnggc`Vxxyyxyyxypa5;#BJ+DL-CK,CJ,_\Fxyyxyyxyyxyyxvg}p_ra|p_ujYyxyyxyyxyyxxngclc`^!sxxxxxxxxw=>+>E)CK,CK,CK,GK0wxxxxxxxxxxxxxwwxxxxxxxxxxxxxugc`6PNLulڛxyyxyyxyymbS6=$BI+CK,CK-AI+}p^yyxyyxyyxyyxyVQ??B+xyyxyyxyyxyyxxnc_]rjexyyxyyxyywBB/39":A'?G*@H*EJ/d_Jzn]tc}p_ldQMN6>E)BI+BJ,@G*^ZExyyxyyxyyxytlda^hca8sxxxxxxxxxxxxwiZTC9<'5;#9@&;B'mfbfxyyyyyyyrrhUYW@HL2=C);B'=C)KN4_\EwmZqyyyyyyxskeuGED}r꜇yyyyyywibNAG,BJ+?F)PP:pfU{m^qfUPN::A&BI+NP7yyyyyyysTQPqjexxyyyyyuSRSPO5wyyyyy}o_>E)CK,CK,QR9xyyyyyyyw@B-lcQyyyyyyyxb^[DqjevyyyyyyPO:BJ,DL-BJ+ym\yyyyyyyyypbvhyyyyyyyywng}rjyyyyys:A'CK,DL-@G*syyyyyyywjcOmeRzo\yyyyyyyyumwnǜyyyyysdE)DL-DL-RS:yyyyyyyyyxx\]A}myyyyyyy{qyo՜yyyyy{m^=D(DL-DL-TU@H+DL-BJ+vfyyyyyyyyywvxyyyyyyywmgSQO1wyyyyyug8?%CK,CK,a]Gyyyyyyyyyj<@)xyyyyyyyxb][?432yoٜyyyyyyaYHpidqxyyyyywUQ?;B'BJ,IN2yiyyyyyzn]?F*PR8yyyyyyyyvmgGED |q朇yyyyyyxkaP:?'>E)BI,e`Jtczjra_[E@G*BJ,TT'7>%8?%8?%8?%BE.TR=rgWyyyyyyxrjek|qjyyyyyyyyyyw~ozk|nuyyyyyyyyytkB@?LJI wn̜yyyyyyyyyyyyyyyyyyyyyyyoVSQSPOwn̜yyyyyyyyyyyyyyyyyyyyyo\XVMKI |qjwyyyyyyyyyyyyyyyyxskVSQngc\|q曆xyyyyyyyyyyyyx}rqide@>=KIG rjeqyoٙwyyyyyyyywzptkfxROM976YVT1rkesskwnƉzpՉzpֆxnsktkfv]YW5=;:??(0 ` 643c\Xetl{pԏ~r~r{pՁtlg_[i:87976xnguxyxyxxxyu{pi?=<[VSC~s眇yxxyxyxxxyxxtb\XKe_[Yvyyyyyyyyyyyyyywld_bZVRBvxxyxxyxyxxxyxxyxwb]YK754~s圇yxxyxuym[c^HVU=QQ:YW@lePudwxyxxtA?=vmfyyyyywlZFK/AI+QQ:xl[sdqfUEG0AH+ukXyyyyy|qj310txyxxpgTAH+CK,_[FxyxxsGI2][Cxyxxyu=;:^XT]xxyxyj@G+CK,JN3vxyxxxsdGI2xyxxyxjc^h}rjyyyy_ZFCK,CK,nfRyyyyyyxqbyyyyyyulyoʛxxyxGJ1CK,BJ,wgyxyxxve_KleP|kyxxyx{qՍ|rߜyyyy=C)DL-AI+qyyyyyyxtb}r^yyyyy~r|qޛxxyx=C)DL-AI+ryxyxxxd_IulXvyxxyx~ryoțxxyxIJ3CK,BJ,{kyxyxxxc_IvnYoyxxyx{q}rjxxyxc\IBI+CK,wmYyxyxxx|m{lqyxxyxul^XTZxyyy}o=C)CK,TU;xyyyyypfTneSyyyyyyjb^e311 txyxxuiYZVS;vxxyxxyxyxxxyxxyxvb\XCe^ZPvxyxxyxyxxxyxxyvkd_Y[VS;}rᜇyyyyyyyyyyyy~sa\XB976 wmgtxyxyxxxytzoh?=<654 b[WZ~skzpɍ}rލ}r߉zpske^Z]:87(  @41/%mc\{q͒t풀t|qpe^742'oe^wyyyyyywsiariyyyyyyyyyyulnd^yyywsbogSkdOxn[~oyyytib1/."wyyxhLO4NP6~q`xig_LBH-vyyw964'j`ZyyrGL0FL/pyyy`ZGxhyyyrh`zpǜyyofSCK,d`Iyyyx}p_}myyy|qΑt眇yy^ZDDL-ulXyyyxqvnXyyytt朇yy^ZECK,wmYyyyyphTqyyytzpŜyyqgUCK,idMyyyy{p]xgyyy|qj`ZyytFJ0JO3tyyy[WBxyyyrg`1/.wyyyjIK3UU+wyx̶̜yW*-ayt.qỹ̶yu+->y[sy̶vyZ+-TysILyv̶̾xyJ--_yoWuyw̶̻xyF--ayvbFxyx̶vyL--ZyhcIxyv̶zy_+-Hyxfkyz̶̏yx4-/ryMOy̶̬yf--@wyh+]y̶ˁy g7,7V^L+,ry̶xyxePFCK[pyxxyxxyx|y|ǠwywDŽRpߟr͉ ȬȅʣʃNJ}ǀwWEEIBHR bDLpVI̶̡gFKtǦ̶ELWl̶ȂjILiac̶̾]KLq~l̶̼ZLLru`̶ƒ_KLlxtb̶̅oIL^vz̶̕LLK_b̶̮vILWwEp̶ˊ xPIOhn_EI̶ue\ZanǤDŽRpߟr͉ ȰȅʩʃǙǀ[A@G;CU j=F{VĄ̶r?CC̶̙@DWx̶ȔvADqdg̶̾cCD}u̶̼_DDa̶—fCDwd̶̕}ADb̶̠JDEgj̶̱CDX?{̶˘ NBMr{f@B̶l_\fyǪDŽRpߟl8mkdd........................ih32 <ȡȒnĵčswywtœuyuňʟyʆɉxyxɅȅyȄȅyȃyukhksyvynK2%&&+LLKfqkWvAKLKcnlJJLLYPv[wZIL|pzwCLLIwfTWILKYARɀHKLHq^E]xJJLGeaAJn ]GJHGZ`XADJHĂ }dTIDBELZlĂ˨ ˶ˊ͎tȣȖ,ll_ա<ȡȒoĵčŝňʦʆɖɅȓȄȔȃ cF88998?Rh˔ \:?BbytX@B?=?CL{@?F@DF`>‘_>DCo8À7CDBg>DDQylk~LBDDdc̄>CDCmb̄9CDCo}sS=CDCixrICDD\O\`ADDH{RɀECDA~g=bFBDAnj=BBĒ kTC=;>I\wĂ˫ ˷ˊ͎tȣȖ,ll_աh8mk tt432CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC1it32-[^ݗ^[\)]lݩl])ۍ\9eǓe9ۍ\(eΦe&ۍa`ΒX~liʍΊͤͮͅĸͨ ˺qjjmqrtw urpmjjsΥ ʱpiovyzzy zzyuoip΢;ojsyzyzysjp͠Ͷiqxzyzxpi͞͸itzyzsi͜ċitzyzti͚lpzyzpmÃjxzyxj͗Ͳoqzyqo͖͠ivzyzvi͕ˑjyiʊkyzk˓ʆmym˒ˉmyl͒lyk͐͠iyi͏Ͳjyi͎ovyvpĎ͂qy{zvlcXSPOPSX`jqx{{yzq͍ͦjy z{r\D1%! !"!" ! %(5GXivz{yj͌mxy zuY8#!$&''('&%##"'4EyxnŌ͌pzy zsK( #&'))*))(())**+**)('$%xyzp͋iyxQ& #')*,,+**6GSZXRG5*(+,+))xyìtyzj0#'*+,+.Mmz{z{znO-(+,-+,vyt͊izyzY#"&),,--,+Ep{y {sC'*,--+,qyzi̓tyzO#'+,-,,[zyzP&*,-+*mys͉izyzM$(+-,.czyzL%*,,&jyzi͓pyzS$(,-,+`zy:',,'gyp͈pyzc!$(,-*Szym')+(]yqʈjzys)"(,-+;xy{F%*(Uyzi΋syzA '+-,+izyk$((Mzys·pyf %*,-+Czy{4%'>{yqˇizyz7!(,-*hyzS!%N{yzi͞oyg %*-+8yhlzyo͆΅uy{A!(,-*TzyzzyuΆqyu&$*-*jyxyyzZ]zys͆jzy_'+-,0wy z[/5889;->{yzjĆjzyzE"),-+={y zT+233231H{yzimyz0$*-*Nzy zS/FWUTTUdzyl͟qyv%&*-*YzyzR0WzypͅΓryl '+-'dyzQ/Xyrͅ΋tyd(,-)fyzN/Yz{zys΅ΈvyY!(,-,)hyzN/IOFKdyu΅ΆwyU"),-,*jyzP,12.Bvyw΅ΆwyR!),-,)iy w]imjK31Ozyw΅ΈvyS"),-,)hy@57wyu΅΋tyW"),-,'eyzQ45nys΅Βry_!(,-(^zyz{zyzS36nyr͟ͅqyj (,-)Rzy n8T{yyzI38wypͅlyr%'+-*E{y g'1q{{o23Ozyzljzy-&+-+6zy g).4KJ2/@uyzijzy{@$*-,+syxdL>44?YwyzkņrxyY"),-,(`y{zxx{zyxs͆Άuyr%(,-*E{y{z{yuΆ͠oyz=%*-+.vyx9,Ixyn͆izyb#),-,'[zyz_ jyzipyz5%+-*6yz6"&(pyr΍syzc#(,-,'\zyze &),xys͇jzyzB$*,-*0uy4")*7yzjpys-'+-,'EzyzU '+)B{yrˈ͔pyze&'+-+&Wzyzj$%*,)Rzyp͈jzyz[%(,-,*'\zyzp,"),,(dyzḯtyzX&(+-,)'Vzyzn-"(,+rys͉izyz])'+,-,)%Brzy z^'"(,--+8zyzi͂tyzh2&*,-,*&*Or{zyz{yf: $(,-)Qzys͊iyzuK('*,-,+($':QafgcZG.!"&),,--+)kyh͎pzy{mD)')+, *(&$"##! #%')+,+*'=yzp͋nxyzpP3(')*+*)( )**++*)*5Ldvyxnƌͧjzy zykS<-('(() ()',4EZoy{yi͌͆qzyz{xndWPFA>>@GQYhry{zyzp͍pvyz{zyvqƎ͵iyi͎ͣiyi͏͔kyj͐lylˉlyl˒ˌkyj˓iyiͤivyzvi͕͵qpzyzpr͖ņjwzyzwi͗ͩnozyoo͙ŏiszyzsi͚ͻiszyzsiΜιipxzyzxoj͞qirxzyzxrjrΠ ˵rinuxzzyzxtnir΢̽tjiloqsuvvusqoliktΥƻͨ̓ͮͤΉ~lhǍ_]ΒX\(fΦf!ۍ\,eǓf,ۍ\(]lݩl]!ۍ\_ߘ{_\[^ݗ^[\)^mݪm^)ۍ\:fǓf:ۍ\)fΦf'ۍaaΓYmjʍΊͤͮͅĸͨ ˺toqv{~ ~{vqouΥʱspyyps΢;rrrr͠Ͷn|{n͞͸oo͜Čno͚o{{oÃrr͗Ͳr}}r͖͠nn͕˒ppʊut˓ʇwv˒ˊvv͒tt͐͠pp͏Ͳnm͎rrĎ̓}zqgb`bgoy|͍ͦqjSB865889::99869BEGFEDDEEGGHGEDA=:{͋n`76GKIBzn͓{b3>EILJGqM@IIBv{͈sq4FCgqΌQ6BHKLKFxy;CC_~·ss4>GKLHXG>AStˇpG9CJLKExb6<_p͞zu3?HKLIOw{y͆΅Q8DJLFfΆt8>GKLKEyjlu͆om4AILJI iCKOOQRHToĆqU9EJLHT cHRVVUUR]qvBHLHFH=Xx͆op:DJLJBlm064yorH@HLFLG8@@~sˇ΍r;DJLIAls5@EE~͇qT>GKLKFFE:DFNqsBBHKLJBXc4AHFWtˈ͔{t=CHLH@gx7:DJJDtnͅh>DHKLJD>f |>8BIKIE͉om?CHKLJD>T l99AHKKHPn̓xG@FIKLJF??_tK5ADHJKJIFBQz͋p~bJACFHHIHGFECBCDEFGHHGEEM`tqƌͧq zeREBCDDE DDCELZl~p͌͆|}sibZVTUV[ckx{͍rsƎ͵nn͎͢pp͏͕ss͐vuˉut˒ˌsr˓poͤnn͕͵s|{s͖Ňqp͗ͩqzyr͙Őom͚ͻonΜιnzzn͞tq~}ptΠ˵uoxxot΢̼voptz}|ztpowΥƻͨ̓ͮͤΊmiǍ_^ΓY\)gΦg!ۍ\,fǓg,ۍ\)^mݩm^"ۍ\_ݘ{_\[^ݗ^[\)^mݪm^)ۍ\:fǓf:ۍ\)fΦf'ۍaaΓYmjʍΊͤͮͅĸͨ˹xv||vyΥʱwzzw΢;v}~w͠Ͷvv͞͸yx͜čwv͚tuÅ~}͗Ͳvv͖͠vu͕˒{{ʊ˓ʇ˒ˋ͒͐͠|z͏Ͳvu͎vvĎ̈́tmjijms~͍ͥ} xZC400223 4205:J^s|͌u tK30369;<<= <;:85438G\uČ͍ c8/48<>??@?>=?@A@@>=;76͋wk516;>@ABB?>L`nvtl`L=<@BCBA>=v͂B/6;?AC@CgiA<@CDAA͊xt33:>ACDCB?^ [:?CDDA@xͅg/5<@CDBAxi9?CDA>͉we/7=ACDBBf8?CA:v͔m.7=ADBAP:AA;͈w06=ADC?n8=A=zwʈ}:4=ADAR]8?m27hy͞/8@CDAN͆·W2wyy͆w|/;ADBF vBINNPRBUuĆ|\2=BD@U nAJMMKLIa{C6@D?h mG`trppq͟49@D>umGtͅΓ0;ADC<lFtͅΌ/hGcj^d΅·o3=CDB? jBHIEECY΅·l2>CDB> ydKJi΅Ίn3>CDB=YOO΅΍r4=CDC:lNO΅Γ|2=BDC<{oLO͟ͅ0m KnbLQͅ5;AD>] :FILj|@9AD@K fUJJWuvņyu3>CDB;~y͆·7]Ά͠R7@DABL=`͆y4=BDB;x|+1.yvI9@D>LI29;wˇΎ5=BDB:y09>A͇}Y7?CDC>DG4=?M|w@:@CDB;^n/:@?Zxˈ͕9m͈ww8=ADC?:x=4=BB=u͆s8=ACDB=9p ?2;ACA@͉xz;<@CDB>7Y z73;ACC@Oẅ́G:?BCDC?9l͊v"d;;@BCDCC@<78Oj~v^@149>BCDDA=u͎ \<<>ABCCB?<863442157:=ABCBA?;T͋ujH;<>@A@>>=;<=>?@?>?LfuŌͧ| nTB<;<<>=<@J_w{͇͌sk_YVVX`lv͍vwƎʹuu͎͢zy͏͕͐ˊ˒ˍ˓zzͤuu͕͵vw͖ň{{͗ͩuv͙Ővu͚ͻxvΜιuu͞w{{xΠ˵yxxx΢˼yu{zuzΥƻͨ̓ͮͤΊmiǍ_^ΓY\)gΦg!ۍ\,fǓg,ۍ\)^mݩm^"ۍ\_ݘ{_\t8mk@?>63RP44@A??41PM42B?ic08M jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 Lt˩ZK'pkOy"w^\}a P {L@@6Tyk=o= T ΢J\Eg%C$O<C]Fɻ:~=4oԍ ɌTH™Az 7&AvfބlCڱ9|s`gej΢7o9ilE~ym1~Eew!2MNBlBj-N]mKh{ !8(Aj. J5Β!VF D$9< a%xY1_ҽK" NeƦcH"iyH>.Ditrsќ?Dyv)M$$*T|EtK =gGhpMj %ᨘNnZ  ܣZ Wӣ5ZWI N0c0+\rvw>Jvc-Z|>C:𦜶oB)(7EU¶,܍ y:#,S=훇;s@`UKIA BTLcov6[4I Lw?h4, aPV\|<$7 CwC-_ 1D RTGď>ZiV{05%^P-Z#Rz;i0w"8%;e*wBXI|J1O(fٷИ,g܈d+z*`B-ll`'ď "! 4K?yKsE44F˸DcRj&D)R0PǠ󶒰mW@ 13"jsHGD2ι%n,H1%AL$]p[)Gy -#r&ovM΀2lLZ[t3b~q!=&r Q@3kk"MӨJ>Z2#{Œ"v| 5kƩX+r .4u̚=G+o :+3{c9w~0yp S9Zɨ8 ^Ɓx8YZ `߷/ZRRVslgi>+ I+ &H5mm(6Ո#>ev#ai{Dg}K=d{5s3Hbmqvu΃U?ةp̷arv;-=krԧc]@[ి$Qp=w)P萎B-\+r6gSi>x 'S?UYg@{̟uن/:,H<ɒ]ŌQT-BO_LDPتEƫ ? [jW=ƓO7BrvLճ  ԑ<"x jدjȝgMqm  >DsDuø3F h~@x 9BeBlH\l"|uǞj}n# h$њib>یX-ln}VQTvEG@;2? h}+`Hyi5t i9l/ӰfJU%3taզDڤMݫ5 XYNәM!GHVg& Nߑ+k(^\88d~Q} y% Lr#2b-A"INF:v@eԈ 7st_1z%'$_&QnajVusuVPc@98BpO?ñe%|$2&UZoN!Ir}wT!ur$"a܉;7\`<>)Y׻oR9d^c=#Da< HUniĖ*N=7d*-1vKD)Vaifb ӭM(hX}Uϯs9]^F5P${N1 bw΀pߦ|hb^:v^}^D+ 'x_:*8ےF]:B[|wkQ}5n>ߦEyl40p D~rQ֏Փwn2ɺusͬItɒl4KKd|*n vFX0j)&3*(^T~D1RhI!Jt'&ƛ/|%eui娋!y..c`ȵYv:p7w>c<m4w .UI"|7FPVE:Jbu1e$ϝIq*+?t13^W Vk7.33Bu|C7O lkܒqƛt w8>X;7u% ~pP_lS0S~Bh@BtH+C]mUšX*ucN@|l>:3C߫;ܵaM\Љ煴qC]`y!gQ`UmpnrdJ;zR|ɚ-\>eK2NFUu`8r]-w9"fum)A-#ɓ CYlnNr7'.[ |5 W{Q."g^GtNďU]ߝBu; sI

ZiߜD-\nM eTRu5"6@0P]u3?pMN/ ߕ4)y!d|Z3 SynXtt@bcPӎz@g(t>lWR򗔾~r6•J[|7|7w(]Q*+ ȺUS3;3@+穆08p:gۺkGЊˆW Oҷ%@a[!>KPM7$}Kj+H@I F>˗ưe}cI<̨1F<υq/Gbo"C۬~CԳ>fG͢jhpJ~4|BP%^C.fěRs- :Yuz+1&8+G 5DL,$qE4Cگ*@ӻm upzAz7ǵa)oGkk)?MoF m5':@({ "/(kox]4kJ}YgUzkgE-Zx!S_xn؝V)³HD I$;ׅFGO@k 7I.T;0gX2kb՛lQM]T̡[_ =yI}t'o_U!2o8wSg]i֘N i u+0,›زp/8IxI.. *ɘ"=н0C9g<;9ԗC}??174et҉Q.&x#xN-x R‹WwSsG-sFJ3Qv-|^+ $Qy.:+CUOgj1o,l܎Xum=\-9aDצT'L~=:G߼RC/*'WC8g&xmTXy*WOw:Bs@nu[ZF4v*гVΉߑG*BzDh8)>@zƪԠOe4af!o^.1` NE;_?rGzA⚰Szv)!W*T59錓: ݡuJ,^BLO9 68$.dvz!e2׊=ho[D^[Ev2pwE+/2% DVxESxUwpX񌂳f߄5˗[X4^E}, tǟrL fǗ:mNTrSLvgYUklߏk$ kQ|׸ L&+dQ6f'7QeV`y)4Q}r90X+ zpo8sry\S-үp3GU4y/QXq$;RXr^sS|(h&Rm=Sęq&j5sdFP9 Y3 ʲ'GYMtRK@۰j-řbA6fQ#m>쐙<<*x\yH/fPpfTTiUJa۠O]lzg(_['>P݊TlHܤ)2i{@yD*$C(*#<@~io畯Ṵ3=Þe}=n6u쓮\"Y0)jV cϊ#嶺|>nj>T/9f5 4_#tyS'!j O;wI0y9el,._ID!+*u3& AA?48G~8WKQ͊JM[,ж*$WDߥ%3ֆOlt'E<ϻg *3ELc3I`ܵ `rUj/S+ (ɘ_!~|5Vy [@YlDHzt7.Q8 #8mtlʝ.ݡc#gqK`N |R-$2K0FH;uLrƕt66R9:DFgC$61T^{UuMI=O`F~;gul5mGL}>V`Z|2~X N{ 6T`BR3%"[ctWYd,4f-VȢՅ{d}XCVZ8vNJSB%{q#2NLZΟ}*mCwѸV~99/~¿!pmD[~hٜZ-AN|qhdFb8WE XH*? 8:ؠ>a70cJ`L8FB2IDHQ?ЍBSypjП| S% @0b2&Yr쎱}+֥2$ľfTx۞hK@xn.KwT`US/Z<aҋ8yWLO"9? x:eHv'ބhgKT~?Cjj+n&wWy #;r9 j9 w&Nb)~ |܏D@с-!5xb -XM60mV|\E it=MUO_& ԲSk/ZA.Y51ٸ-8׾ޔn& h?HU 9fZNҶ":>G`ނ'<כnec?OqHLtw _C]\O^zkrҘ~vل{}!_8 OVvwm'Z"?9bϙ99FhکѼ1;kvG,㠜`&$Z$ k#{X"(pf]wv &>Iʕf}e먚BL,V*ٮQ\ԗK̤ЃoCEH; ZqNge kd j^@V--;5X}0*D|km6j=أh)f='ng xr`p-zv}Lot&s!S* k (Aט9pZ\M %CYmb&G$30v/{PnBJ'U?#xȗaO};@Xj֚NN[T9"=SXH=#yȁN|H͏܉>wpþ%pc*8fZ9KK:͛Cʠؔך+'&ݑ P z2%8ziQW5:%;/qbM=Wɶ E#!(UPl,(Tsd]hF~gsmyR&sxxX \yݓ1Vv-7$Į/ƘSPq+ZFJ?O92&S1B_:>XLBfRQpxb!H94&WH.naKneΟ`N52R57$^\Xg -٩p.W:٠ԃ:oXI=hsӓt3^gO_ r|T^)Q燇a Nˍ;$r`y];cRdjGA.r3YYoyY6QȺ+U#0ZHęJOGYekʨʋI4.D`r5rǩgA_ofd.x%H@43JLZb)qF ,yHr'NL`iOȞUUUUUUUF-cB&p'` EmOC^b1E֭$t@jjñ@0wiGPlWd 3Pm;ܒI$I$I$I$C9^ZA-He8lw"@%TS p 6>l";_= 1v}j v,s*%N6yHED DKہcH%=@wWc7ڕl{/}Z65BCxBrY4*̟ M4Oq7WzcYi$_ Ů /YSIm} Z@rM5ff fݎcN /ߘte| <h厏Y(ٞbL*cТsfuZHbYbrmOw஑X=fNFGB\Ӏ(ت=<9+'N?|f$XGen+v,CM6*jE/[V# /ˢcZ&Z#52=2r-_FBY6;Z[zKbPWl[p[nkʇkl*sd= }u˰KkS~kL āwj犹Àn0%=KĬ_>\/?De䑨T@z T"1Bv,MK6,:eKa{k-Ci[7k ָ4s&҃ &iϕL31< x2BocT?I7Ҋ-oC+ס?X+zqMf4LQZb,NL[Z>\Teq|Qe;N!~O |I%pga|֥ϥ93_<8e'Y^v#n 3[El6o !w8ܤ0 ZsG7PyyMV<u:aZ,pBYϼac(\Tƴ* %J³qdH/{~`a?y: zo#JO9;"fv7z$VYqk:i. qe/2#lP֢W}L@Z_.{\!$`06@wob<.<8uY(%`q'- j:0;E$uV:z5ZabMS954e(-%L&08(s>AɄzO.5ڵmӭ߯+s(+&@g-^ّz v"8b7Ga3[;[vjSI)4P8<H^:evd~?̞U G}̒H|yY"l߼ I8asv?_}hë;fsOwGA裎V#M5:1^EMɨMzvV >!h1/ֳyZ $lGJhNѼL넿/m'kG^?(:^d* Fl%cn&<җ҃/?=ד!TҚa",a\ck24-1|;TVurb\T#?SH0~>7-+!&E*%j-PmK03(`GmQ77%1i,lǎif0qM} V57@͕Ǚ\t>9e1J'd\~&>b!;;a=Hf>Y1?jjKT*=*)..i.O۬],!f@&kCWW{vCCwpىUKZ,"(%8:M] qEP9,Opr [ʴp ś8)} ʇ3a<>ݡ)>N=J/ZϋFp 5_ܤMNӵ `nnF ".^ r,Rr{Z̟IARiR!,*?Q+/֑Tx,./'Vhx:2)?E9) tY@vE0r(|ޱظKЙ?R"wِsgI{NF7hvx;[etv U׬+)Et,ap|]30C6BWהYMB~2wmBd[=dh_W-!K4?|<.ۊ&maSn50^{z#_2= Lql &zR3_m&] Oq%2l+X.wJY,)p)a,0 2rLoU7/֕qE&duY kS ?RǜHBS@⪕&xDm_k!CaWs p/(߼c^ktq%%ᒅ`zH6ѯ 6Vzi␹`3vSn}N:Z^>v}pM*C+,9cgt%Gɭe8>}n~Đ6?g+ؕg'bt8l|>!pUP.nպWk,:/~ qrl8٧P؄eFE.Ïrđz?#ed 2\#V~-34 FsHpp`*U_^8!j6TD|qNդ2GA\cgYviC{>M>LE'rl-}Big\/AQnR%ONRmbHLou{\a5z"lqTAYq ]DŽL ǀ;vQxNץe'{YطUygm/(zrv89W5=.2 @09D/īGԯH'3#]'y7TtCsW݅ 6Lض1a.XȬ&Earچ'Q*eꉄ l4_oŹFb6ts {x:^:j6Lz}ՄSR_͚f+9yC !;~m(Tկ #JR466Qa z,K H.։ulw¸3ԯ]> 6d- &kW\8h^!$],sg BFl@F"f7L`xڡ?d˔A ?$/5: 2GesԈYBP~ͻώ9ujOilUJcS75UEdJJ3YЀ C}ZY^[1no* GMOdEx)GL sd j԰il?f 9܋|}aqUQa!V){lu.5KM_u*4Jf~a˟/- &Q%^2KyUh֕Zj1c*Ă,JG}9͗KubaSO_UMG|={|=|=[5-ezE~=H_8qvky h[|Nɨ!ͶK㝫Lz=|5_OG^"(g̜&i73SY̵i7SȻg2 d<]k$cVsbw˘zBK'ZL-Tm_ +ވR$Ckc86ݒ:6YE8~ E^;i?=-* pV!8ZJ|YYP p%nRM`3lu~/UIv[ȧ(Xʛ65}2A,Řd4gc}\d0t*N{X4^SżrvHwKCvQ4[rjR9D ?rGdKV0gυ5&~c"Oc2« hJ6JZKE߶sah?rv` Xv>-=9ubTWG s Rj~Hɲ٫at}^f+ki_$KW*R;HF;j@Y` bCy^0g֞xK%mK$r/{*BFqjvth+WTRRD |%1Cl3J!@8efd^ۿ2$Cv<)[_9h!8 Eٷ랑(T9~ QQ\~/>mĎoE}fo=7e#q9!>.۶+> SM0=3sjWtG  #GBR_F>`9▤SsCPpUB]}vIo;[G?:~]JW͠%'Dvѣ1w7.JE3zhyA@#lo>̜Ǘj#@.EL&Hf>=!8,x2!,G֐ l3l)tLuͩw:5 DU$!,čYWߍ#t9vޖ؟ v^-=V,:T{jR6}Fhg ?T;rR Cԟچ9#Jq+FTRZ<hS ۷ld} 1,w+p&[NzkL0 ̹cɄC!XzNGAsT;N 'EYjAARk>vH>)a3x/ Ƃ$ySb!`Go{³s!dpk%|ϯ{!odɿ>ADx ./J7 z%sQաr7ǹ_p-r~Gd\FD%Df ?`Ɂ2G8t(Cy\b );.$Fe$۬(SpWf4 _mPkq9/,lnL֗ PX`2~Ơ(ZƃX͌}Whb1Ź%Q2qDtt2jiksBEr挣t\SSBO}*aHn7cX4mtX]򼂨%jC͋WNoLgZp[J|.ۓM-į g}3ۗv#ܯ!-~v7b4 gDA[.Hzg"rNmŢA~JD PYHH?jkJFcko7E')<| &dfyrXÎ[ӻ3qs ̝;L_;hң[Y@5փRXؑ)d|9W4J2TbX"Rp[ ^ >,K!SM#aJ,wKbi^K֘rі=1Z$[lB[SOS VrP.•|p ҋ$6/i:^5)&Ʒ,ЁaEξ^͏ _䧰L8;6lkF=åwi8: &P,07J~寬zD_AHѤg *c+e#bcR)~mZ|@u> N Ƨ{KnlrWJx2D=[/ϓX -1NbEG'F6M&KxSxQ8q59a mv؂ 4TMj'ǃ ~D-|/1k3ugBr4UHϒ=CR= H}J>{5s3Hbmq΀ӥN依ٚ—'#k8m&ՔP N~,u1CGe℔ zwoKeV|2‚gW"L= t6q@sō6`~X@hא2TA=;ALɏ<ZpDw1_/#-zӂwxmNӒ 1qوe-? P Br`PzP|#U͘Ї_ p@Dlr|h=&秀pJ1TyDx}H)`= W&sQ 1m2OIWb.UHٖ.W,/X+ba*gIQ'½xD=*Lɓ׃~.;T4 .XSn]˜ȠFbKm>ɢ͛6 y&S(hrH 9ulfWBdf|S w?0[k<>b7 tW+l&hxǻtaddS4wF ٩sGOU>ӫʸ6 >\qV*bE!,/ZleżH.?-+ A洇ުTqQeizRAbUD;3Ux$cᳱln\8> ™1rJHWKtZVo aXPiRKڇ,g$KZ<@5E,D {Y`SРAR+II-2P 3cDϛ52xx]E"3NiBe4THtL͉G:aF~dr(s6j'k^zk.n@Anam[`^i:P ,ܮu[lXDݔrcac"kPׯGӥ2̚8ȿE4=zoBјY(Fra:ID')jk' ? tRx*KD m߶m$)^8 ʈ,B,X@G7m!Pйc8d'ΑG܁`;OEƻBN:n6ܒI$>?);Ikaf`\5ܵKȞtԒu!b/%INF:v@eԗr0r"zn|Wh 'Qy 2+?g|$%YyIx`JZ2BpЃ1E%XP}Uϯt@箷ݦOEc%b `% LYl5S2Y_LH.~~ΒK~]U1 |;'zDa׼+9}j~҉h.>LUn#r ۥ jXTOR+ռ*sYs~?\w $$)\+:[hɒHD3$;~nb8MrXidvFS>*[۪ "OD)Y8ۄD,A^x0|I-:;8؉L=_,0{զ͎ȹVgCb >/q˵q&>[C F6P<W/. TOd є1!ۯ :JbkA4_%l=$=MSW ]2 wgyЄ\\yZX"rTyGe߷ kT6FY㖮 £z"r_T<Zv¹㫟ڋp~FY` 5GM.!zA8j#SQ*{qnO19MzM@|s d,0~gGa.z@>8λ,7vKNFznqpEά/:C/Djɓ ,ln6l1Ӎ[v #(,꒭Np⾽EԤϋ,۝sa5خRnLi|, 󡭮,M&ew)1e+E2DYxo^!O"Yn`3߸hHCXs<^!kpe AIje^MjdS7u*T)\-{4@)ښ"ӻSY>Rd? !EA,&/aiP3,ƿ%E4v pz0?!v޿̎Sn̒zi]4?P!}: eXMq~Bb RIY',($+iGSjq%0bE|I0/)yK[_7˦L=}m%[hЃjr̓ai(]Q*"GmVC-JfMo,%GJJT HڇzN (T ~TNE"vYtj(&" +kE5t;w}`6CnZnp{8rJa6/Q~DP~A>fG͋`t$h$(2FY5;ό`jƘeJT`w4[ mWӴ~\Y=˿m~T8 ~l-;ɦ$v1wr]4Mgmȹ7fh/DQ6Pq[6DMSÊ* 6t$ tn)&)+89Cc/|>uCĠ"}j,idH)T:N1eo˴u3:\"[mn<56& *rF' 7Jxʫ'aDA̚l 9SP4miuq+6? ?qDRݩ+}D =p2/Zv;oacb`H7PG z]4ar;jw'1# La:hwu$HgU `mHm`[\GUMJ~rM' -9(2K^wmFߺ4V OVCdeǢv&|OfojYr<̣H Ǡ/ [iBdv3F_Vݦ=DЁNh+ELl1qmc4#'97tE. ;ǀE5͎@v'(]wQVQ68abl4Pkާ^qv_>  zK \~ֈ}P +`b)lAwUD!^|kǨRu\p6 !m!%pRh gdlSX#v/Cؔ=fݔesnK4NΨR\-ov]&oMV{d( N~M\Uy%H@Q3IüIG|sj9Ձ8ڗp")OQkI+r#S TiLT E=X_ ! 7sjM"k|k o-/QI4>4F 8$!>{̳_$n8# d\+jw@TuFCe^>LÊE]}tQs5jYE\ן>fAj}Uku~\UR!^ GaƎuhb%T78yRɢAbųۣr1"GS7Zğ[e=IJK^;B4!'P` !,m\ў挎ڠ:hnFmvht~/̆\ X6΅$ pdTK|KRt'ƚz+V{ 6FdʓQ@説[T&({E }7]Ǽ_xĶIS lA֫%:Cꋜ^g\TȎɹR| +cȤ^'AfC[ePБڽ #Gw0*zς.#.(cDR]3nu 1ܽy#˦:g@;)泽ډC2_ xGrw ӆ ! k2 tKtө!Euد\ۼxՒQcݣu9z'i-N\n?ð*˓w U{Ln"ⓔJ pn%APW.y~4ӽr^iOPiÌcX|~1UYJ\NYQئui ߛ%P 1e##0᫑ .(q_ٶ"ZjZjEB9\2ޝ^rzP=JQ^qpE@kG6O}gY 3 zL~ /OS~Gu?7&6ECM\`-NʾNKp QUK@uH<ʱ[·>bޖO3gUʲ KRTF-wEƷ%$*zcx+xGq]9ne8X=;7{a_HsABgWfm".]DLP CH]n%^,/:h(lV蛚.QJTmr }ƺjx+Ԕ_AfU@lua+lB/wNܛI 3+#w׸9"8zW>6% $+ڪ?{,w{$ߣTcۊ[!Ͽd}BK.ݲ$PUŠl9Ca⶝CyD-"$z?CfQ4O52J0.p ET*\*"E{aB^cfpI۩(~=|+tH+q)/ؽ]hi4ZdF}@;=z އyx{.IA= 0jh-4FT 58R':\&6<k{n0 Hi1`u , fX:HMeV],QQc%n_W[J@gex!o~^B }W0.7%^#gqax$Й_=@BV5Dg2ABͅ55wYD=7*`*ӻ *qU#ͮ8fKh IncgښT`,\FLNPw#PV_wZ #Ho^ߗcSge S׵4pbHՎ\h5Z uݞ(ekBo(dn&@apL쥤C/`K$V.;YOIEޠ&pO^ȑUm41ceON`jȫ@dq.V L+x B2|o::OG!оtAJJs_ه.wm *X"WQ1] ɏ$ئ,Aun%hqU9 \qy/ DYBZq)]IsF) nPǩ B+U]r.#jv7버fsqF ޮ.\z0>KDl/REEƚUieBO)@u7h~er}4ŋӦ w_b ʀڬG?xsjHn?$Rgo#٧ܾ}Ml_}ߜ9$_;I:i"ɕ_/_cvi2|mwKgW8-z'5 k$Mq65*zo=T^Kփ~xd 2eePp:W"Ưs:˃_&DC3΍nt?Z`΍-y_ ^ ,qHݾԅ0SBVP;e4H"鳘sO#|r!#ᆸ߬ï ȡKEgR*z8>?mbXFx#31d?qN{m~Mla^MŅH/c'+2ipo7庠W9o]s J9^HkS#9h#=@hQ, P</AH~8EfoGA,k7JEj^YŽ ?e;@(QWi rp+۝ 8yP}ppK']'29=V D9h^gU#M ~~~~"8\5ݮ)X2y0 m;l/vŌȐkonium.մV<}m.մYYyCfJ-wX8~ `)52 2-!'uׯ#  kǜ>VE'{w^>{T J0 dQ1fL]tnLK/ '(,4sf>+YM70n$%W.y_Ҟ|#uN|ʵR{ϳ4Sή y◈ذ ,S2%t}9ߐgD(?ݵ-pf_ީw߯۾~}[wOk` N?IVnevA C E/bɳŽyP[dos|{>ː9~Q<,3I"":Gs!eǘh%3{>ݽ S=,>~PTxS&TxE(>/7zB5o[a.fh14Pʪܫl%F&~4 %.qY&~8ڻW=x|Pl^׌sw1HR еQJLڸcis04Cj"K1'gHJB9N(6P7 wKGI,$3c2`,P-.M\Bܺvr|ohZ6[ <dG%zOTXQӾYƒdҭ0&1j^rLPbS`MG}?LsV)6ԡ&)~ &w #x"A+O%z#6\P,ocWc{6ĊFص[E P.@FU d|`#섭+FbErL6qHp^iuelRyyz-G tm'gSLbW8$Mj]Kw3DREK`2ap8;+-@6yRcmm!vG r&43(&* r)ƁremCǿ!ۊO}"z6ō ~/ ߼d1խ]3tc!ܯ}<bh~(j*'wa4 Lԃ=<tO=bB@d%޹*F;kƎ^: U7&2i5UNSo7bMNj~w;5X}m>{^KXMw͈/kvOr<3T񜂄FaV&=BtA)km,5?SzgO$~4Zڪv u3GTrւ۪[^i7lWV@O{ɗ޽qBZ*2;r9& ϕIPBUX#Q-,4H`Ub:MA3k&x]) "kgdG (=Ns˳ptv6fYZՑJ؛pB92kOt+LG\+@6]Wf><tFi!Sdb#BD|ˁyRe{q^|s/@,ьlq{h QpSP66Mp۩YcȪS6 dcWbp`c*̇kdVSH(\k/}]2kTʖ$Q\#e[TIi%M?o"0p8n)LTrA[)`9X!E}u|2lv/D_rApil}H݇-јI$I$I$I$I4zr̞|>V %]1#d$I*c*7E-d*ddI$I$I$I$I"%CUJuj6Udd~\. 0$,]p9~8bv LiRm8dRPrI$I$I$I$I .`7G!/kƯgR,T30P^sR7J8ID@ 5( Hbf쨗_YA~Ԗl| `h6I$I$I$I$4CWrI 1ND )N]]M^s7 ѯ2*Di>mmmmmnKZPc;pN@^(|pwHZ8|!sX`ssI?ԩʰ $I$I$I$I$I$la4Sh D8*ċ*gubMZ?by+uE˪¢92Qa3oDr ҼQf]ЬoBY3,Q22xF15H_ ̑&&yG~+$JZܟd.ډ cßTP؋dQ\ƇĹeV YA! yK1#|)QHR|2q+P?- 2/~&zmZCu!}n,rmYb@JM ,o m:H"J)r]/M%?LTìaxҩwS[ \mY+A=$uƶmZGZlߣvD=xc' xe$\r~;esx `R? s\:{.BmS]6:bsVxrLs.q!`kЬ:WgefI"e4i}vؖg렐q G=kφA4W51*hk:G49a9'Y.aP͍JzU͏ ږ񏟪"mgϨh'`xh|>hMm Vk#sMPd޸mVRBifdQzl2Hda] x`d23PT=W'Ē7AMbeӃ,*Y+M6+*1?f1ҋ#K(ET%h D!h'tU Lu]7`=#:1jaA$w!B(r0݀C5g@RVIL[)ߢ0'cđNrc{p]% L@7*=ۿrYv͖{+d]Ug1{L**60ReRb,䗜68+9׺Xeāl_p8_2餲:@=- E:@p[iG%Ն>%AG,_$6u%o A2R?Jʿ!&t2k# -M_VI-.0y|h3uBB76F51#٣HŧۡCy^'BЮKܝM#됕i >&ƃuEbޱ?&mɶg~M6Oۘɮ&WrZ {r T:/⓴4Jb&DL9[%Y7.l:E6)V nFw}E+4[g[ߌIؾD&rJHheLRUj>aQ4S-R\[7yZ. `UlxR,d+WРU>A 6U b"EJykml`SqJ[vKAcS@"՛d⹹!i)Ei}Q͢j ~`< LEp*.ro@=s\k+C>'1JG宦NGedzXbYAi9RԞ8) FȂa-]`*8͊+]s+b3v.]I &{@*B`ۣ͵vH"Tmxh 18 nV<+B:W LdU,&ԏg-HC"KmB*;NIZQnNXsm83*Ky]e|D:kA(6=3o  6[xY&zd72.M}9mZ΍H˻RF}ĝL]P/0\][VOTVL٘aGxKHjk:~S3%>%u^7p!UP.nո)N$p'k2\=c΁gR %-h= S}6`OZ0vl}8nzC6 x H4 4N!գN"Z]AtrҊxn$E@w{ŽP3Q4"M:+U|\D.W|7uhy>Op pnCoTkEb߼,`5?wmFv>VI 6ʶ:&8Īa)~=E{3K8Xw(CCwn+):Si%* Ld&Α((.-lPAθ82*A854.Fق_=ld^CQ)s3VD[c&U8[~~\1*m˻Ef)=nѰ bLI e;R *p}o֯* sh0OF-s-u4.iN xpm공ŭ6t!Cn|t|SK|x>#54 ֣GN rŝ G, xW0# m-Vb(7\Քo\u]. B3)%;)Q3XeR 90F8HʢNyf$GG-A xսPȄɷ+g /PS9NUAŶ0%1Jd> #4o\WGg?& C/YZC\ [J TCUJ0."b氒;^ܟpcu~+\UiUL؇0hȻ32O.ERPH;w}EK/&˾_]_ZZM~ӟXfըH{r>O,㪷e]"! r #H\dDVhOT i dYmeEMk\DHt&=YCQshcl4 uNX &R kZJUWa7RF2Vۙ/BY T_(O/num$CO`wό$d_T?1}d<I:HUHOpfC9|KQӷpj` ԡ\qM;2s "ER4)̊/dq֪yI'"0:8 C(p):K+ft*N?KհM Z_> /VwϯútӔ=Q@BGg5\Q*{|bڝ ZT'%OG:=6جߚ.2Hb/p2?zC_ϸ H[W!ȉyJ"DOIj˂6FR׀f>l ȧmDi(f+gnKu@m+ 햅ƨ:՛pP=}OT23^8 KG/HC>%a_3d&CSr /` T:J+z\#=Ý%NOD8$҃!1P^ܡ#賓AfPב?53oA(QݵIDa8ը6+c =OʧѰ|5p'>V8Q d }2 xK̺ rx[~,S#(gRo0+BY@g<g)Ʊ%٭dzTuv&8j:Rn^5"z]mshŚUI y#А]YԌծUB6WE`F@EqF]t)Hsy &Ѭ l}\ptmjgo)Rw!ٝcv(rŗȡMG߭o Ia>,,L7*4aDmp0=iBѡ?Ew )wO(_ِ艎}ܺ: pi*KZ^'"W|nˏ())\T%\Yw'3Oa"./x2S` l6 {$Ɂb֋"j%%: dRw#ON ̻JxJ?lS9-s\y͠s\Ұs/1 A!0~;!ŮΜv%5t `J2_ ֿ/fE p71ehuB"2 |z{i7Bk{?!b&UtڽĚn #-> NهrrgE2i˨*M oùYFDT)5X~Yf2lmjuXOl@zVXUI- Y'&UqS+ZT.SM :S cz`5py7b>4V,Ss䝻r,g(ڊV pwna0- Xmr K w5e6ZrVs0z}(A͋Tֶ^嚘 ^YQ:9խx)"iQq`qN^3 \ic>`?ܙ\nTGDG7 %ƚ)ȗ~agw >8 J>aFETxd;re~!$X)gBOCs-d 1jd X _~Z?/4>ȏo82?]hL1/_/>`Ix`e!"|1CwL)2*Qwa  ϰ D^%oookY ۡȽFTTQr f W {B&}%eY$ᾘ"RB]oK拢wJz;`}as%%ks>Nh*HTä~sab`avQ#Ve*LoB[<ϭd{{ !G~~œՎTK X__7Gj5t 6=0$28qE0a (,eۜ}覀`B_O߫Bn(ܬd*̚dZ̝;QOo n7y^W2|wĚa /\PߎtuzD9qGЃG@`o<MW "O)Z|+iZ._8jT.=[(.2|Lh@B%ͨ؇zS0 з}M`ԤG#^r %7ĨɋŦm6:uZ{|@x c9y\yB(eʳ8zkl^"XXdϴrAh.憨%0v0(Į ێ^v@n4e~MQKd&e;g0"k tx~S KsG7 vUsARwQdy6xz @ dcp[s tHD<`n}uM_7l~ນ,Ë̇>}il[{{>C𾭝Yl~OT{o__U{}[}[O}V7>}}[]V'ϰm}Z}Zl]}{ϓV:q>MWϓYO:|0ϓZ_>L`䫰}#-0o߱,xoD,)}}o^$ qzy} rmz*#!>Xk_'ukȢ8U_MdF)yK^RKw@k5 (I2.Z.&wM()/)yHAz}x&CwC¸*9Rܫ]@_*T @s֧;tX-o4ZI,gkcٛ'OA0b. =Báο$d:XO0{}؞]$ؚXqrOܼt 's\%ǂy[y_FYuw$*@KB+n|bvf9Ža5c8"C I[]2>MZ'fITk1Ԇ/%ֹ݋.w`I/Ǎ ^j },R|ggѽf糯(_oDZ'6$ HvLBx{̭SlR2ݧ+VL x8y$M wQ>_[~}"şf,ku|7wO i]רSaP%0<ǣ-f!׹Vf68<}?{$E)4v N~pRheӜ`D*[Kium.մm/J[f.`WtOϸd{xLdă-傿ꉚoîX hM@SB4kb}:Y5܁bHO3$@;C`y!N))[nuͣ!B%|0Pq$D"sϋ(pr <@>!* QG.{趋8JbvLhGϩm\yNړ1TJ@t9%&T[bț= r2ܢl[Z6hsEll A+~6(ϴЀ{.D[Cz^2.86MQKq=&M-ICTÔbσ!iOϋ2s#&aWSc{'ץ{ŠR;V $ޡ^A(X !m7*Oh'Q pωAIH # 2ĔG |聀="eb n>iϼ)"]"}(;!w NQ\DF$7LR4;+ Cʗ$j6aRu~1[vE_1HTw4gfE8*N9A5P ;f0nS- 'WTth{Mwn ,0]q@IQfwd);qLH5uw {JXHz#vT W^T\=/!TAW2{* (M%ޘne O+42sXbUu;;I%ɺxs'Vs9KYz'5Ur = N O1|mhрb.U>bKO4?wOuh^܌#f壸 :L/Ւ)o,%s#y1apVyJT3}s;o0 )'t `neu.ET'ޮs+ 9j=oG~'{1мjf/GK+ʯ 6dB 6d!vgAe,ݥ!R̵0JN2fY. Z- U ͵f-3V"pѥ-^UUΝ<LQuBoPl=L7Ų>qDjeOM|3AWpYV%JK!ïȉEEvԽpt.+vĮ'BsrBB`K=f8ҳ Ak$t6ێYtA Z=%'Dg 3*`*0%8]jÎ15(36~qL#jp-/T0-)O&Zk5e/<$C$ \OKic-4A,@Z}p[@vco%V59Q5GHNCa_\ѪoyB,D~vDK4ly{xy<"3"n^SHSO&[-.:#rvD,Ifr# wVj!!r~OyrۖUrي#AOnTdLR߱UUoVhx5߉6 ]x?v5% C{UR3@dĂj7'0ͽGCI ICau2}y^j'f$jHc$χX!5y|[x[0..E !r*R AH->E,p1au8'ccdpF60/ f٘Lu碤p!"+j|3˿Bp—=cha9W͑$2?%1tKx}Z)*K &ZK&fWI1Kh=7.f=4=kh.;zٯ/}䇌Y mf³X*t:9wipZQ>vxyThQokNiic"; 7А[LDf.\+wô|d~D6 [*Ȭ@ ړB~2/8Q%6 z'#t4nb l4aDN>{;BF W[bM8Y5&)2sPJ "cy'@__'A}_{\_QƉ\F('~S~ibJ'`2 p6r/qѤ[rRy;ZB{_#. 7S:ap\CQ(U:;@^.!w]Kp@n1_>'t[:'/y`{x* a˖LaHH!ȅr" T@-k|p*EUXCծGbb+%s]ر;:ZÔô;n7_|}Y%kǀ)9 "Xʧ e("#~q:㷩=r $ :!ெpv.Οu >.K0nr!Btz6XAjg]6u c`X*w4[ـΏ\l4p Ei;.񔈚o0 MbdIkTvK\qi&S壣q2uzWy f,{ Id?H#r<t$eN|M@y1p#: jCR+4C%źU8Rj5 } y7iys1gwEn"ЗconGm>y7(Bf!Di(Q PU~2o@N/)yKTO*u BJl΍YFB=s*!}ⲗ/)yKΦ-5'ž]'Y;B)7?c[V/HydXC2夸@Ñ%[hffr OQY3ê+n<9<_trƧ>JGf:"?^}A0n()ΞNjXIooə> {Z'PÐ2l*W{xQUX߃p7wopsgM[o{$b x&\8%T϶O8 U?rB] ^iinw9=4Yes^| o%acf}%zO0,)(;B W2GJw&3_nDEƄV9wM])4 *+m|KQ[r3Ns~ǗIb aw<"r$I$I$I$I$I$I$I$uH$ wflo޶$׈Oe\];/c!çJ;PV-θPI$I$I$I$I$I$I$HTzyJZaԃ:^!)tWɫU0dD- AtHUT1^~W_{Y 9 x. !a|I/b͐=`kd@YrR?#z& q ̟`#E9IP>C h}~wgےI$I$I!kf?8)9hcJrےI$I$I9@t:\|=dZU9~eToD8ھn0n,ֿqPbӼ8ʞe]- ŨתU[LJfQ˙ DvPk8E_gJaӈSmd)$s`&7j!0(DyWvs8lA,Gt /ѕGD(څx 6 o|JwE{&Zodّvcb$ɘhPlYG /Bl+]IWA\î0Uti}%gg&=d0ب^ J.T85}Hgݚ^ G]=mViМbZ' 9OܡmR{ъ5!2r d\Dc/wke̯Ul`:y i`a5u oUɘ𔪽ٴYe^o_y)۵pX{tq Ư 87bIUS: "#ԵE5̗G8y\f)G~|1sYvwvLk`(+9s?ʰPj J:,¡s:XHd쟮GGH^Lq] gOϷL{oST!E0y]Ɏfgdv&tx*lI 5^>lܔ9ѿ1u2ku>%̌=RT}ڡW>B}+Xud/P4}/`R\ $^k]ɸƦ({pu] 5Ng=ĔΗ<3JkߞׯҬ#u+O4MOhxUzÐ2`/6,Iłt=~BUz0 d\`iq[.=}1" QqSГkzg|i`궺M~޶[4\>v2(5^=$e*kݷ0VpETS m >cH.RZG rLgwj~8A+Jch%ǎn,ovUpV׭4ޖFTIeG2Wbs̏\!uR'6ZM+sJrTA]xs5Zss?ptxض'ѷQN+QVyRC8INXƥ.s[j%v5Kh5?*nTEt)G=p,ݨ"ëy\dsqiE"-=Ἐ [REFn4-Bb:+;J ǤPHʶmnq.2+4IwCB u|{Ft&qq3p) nΚ-_d)ڽV6}蝵N,hR1z ._4 ܢX;2DI-/ &ތc)5!,>AXq~֣PEԹ >6Yv񅴬| 7Y?8pz$Dn*E5ȁYqL?Sw3F|CiŬp-Iآ^ y' sh-_F!SHjCk  ,Dg9d9zUK'yEiS^>?;yco iTQF%+w?Б;^a rku$,>/XtDm .i5ۍho4oϫu|W ~(_ᱶmmmmmmmjT;@[yJZl7`qI?; U'upm@ ,PKQn! "a!)Oln-2_F&!A%P~.zV0&6"(5k^Ο` 7Kh_Bnڷfҗ0C 6ӎKIK%ZK^|Zҡ)`6^__WGuNS찁rh ff!) {%Ua喕}/$0E? ]C?%5hH E&'?;T*p36>|OPHx;^}nmO:Q?`F{BL0uؔEǚFnMi\Ncc>w_ ҃wBr< He[|A~)I!X~6ϋFMbrJutR5-"\iO5i-D lcVtifc&ۥ&4/IP[zh q1oCX-FtJ8sh TuU hx jY*yeVV|J+.ׁ@/*a7J"Oq/`eQ|8&LnttmڬW*)s ~ND43%"qHi:L*ޤH7 [$t)J vY8`Qu$fYR$jssJ>E Wº(ӄ hwp"nOd<̀$DNY r^8Nr\"$̛.[H&6|),xKVQѐOtDG!() a_>lCttcsU?m1nȄ>|=dJFzťdt[&֬d\Z߽ *ւJdM uE[ ! !_+;nzN&Q3_ks]aծYlW;=mHq"H[#)- f71u W~Vۇ}gB)4nXv$8^N@Gh <˽oŚt6YqQL9]n"5VƂϞ՝%2p#v!Yyޫ3]m01,neCS1oj^Yͨf0 #j' 4 ܖ0^v٪%Qc`ZP.9/>QPRݠyB0wUd kV5"5'`]5:+TWɾbqk İt^Ech|)R'Ѫ#\Ucߍu銌i3zЅM/x]1K#XY)Jvz>o9m+ +,`dw:>= L엥iի6ٟv\wCCF ߂3od{=DVd;2fdE^_VtLg>fVW" PĺF/(L=Z{锸Q]lLV]Y~-AMk_cRvH*:0VŔ.nPZ<l9 XkU'$qCuIWf^ =Soh&# uX 89?ϰ7]0]FM+ 9( "Z/ہԛ`vrQIp% xZI@TQMLэY\TZ)̴管nZcS %vo~bKaOGF(ӽ$~Xl㯠$m{N&'~fE;Y͛seE]}xaJm ɟ"">&LIhߌdqnM+ŒU[ۗP:[!ѱCI9ǧ"3o@n'e?=-Qx9_<4BM8=Ճ F_Wܣ|la%'+xfg=Rϴ.ZgQэ 9kѪoߒ:U: +~NIӿl_,m-/Y& _7tOMA, ?=t{O=wL]n,ʍSh ԥt$[ $Wq/c! VϓÎ֡p l5-O膕%?qW+|saBKzMv9`!P7eck̘0 ZنWl Csˏ xJDHx_ 9Lx1^Ni0L)M:\zdps}1Yش %K(,ާk ,[qTAۙc ;xk Sa+^\M0V^M6A}HXh` nj5ϵux9TgY: t[O)Թm;.I[:sC> >}E@NE( JA5ڿjރIho)-R1#\ 9lzzӛx$t;3vj2}T`rouOpDn{4f͓@mX ?0 UQJPЎȻ_|}}&–'V> B]E] C+(eX8.6ɈY;ת<($&*%^y; HYB'VHeBCX]O|D P~Q0]ſr0dTQTӭ5dFJ#!DK4=aߵ>yk{!b4v!4wE"]+lc| h/d ?wgSCߞ㣣7F+h)񸊿hkq+C>a_K7yԍ#ֶt8+7TGh667Zt|)XBG 5)$3K9c%m ` 9&AܙUV8#07;r~c`96.[-pyIX!8 ]-ֈV̄v }K8Ini,{mPGKuH|&u-؜fxZ69BgaXbF9w_$zeBDa ۭS ϟC:o+оא:7"u9g\=P!-G^F{ X@G(8;WmTOʅW)>ED1&*$jz29hUlJkяle!H⇿"8FMtI:e|ٕؓ#x" 8orV6e%Ԁɧf9-cuܳfԠUKD"OJ\WTL6Vb*W#}wjϓ9wr?}`AZ-i § -JuBaz b6bƕAC7#YHҼ4!Rgb'vdK~7%!)$l>,;\@cILsa3FS$Ե#j]nKǦ0JK:ʢŃ\pvx07`yKU@V*-,pV6XS`28zqݪRЗ:Biu)1#[Sc,hYM 4@x[xbbBp<;@sG$mcoAR~H iLWBx/-k4EՉ hrkȶN,8? [̨]I;ȥ͔ /LDHHSntͭi:DY+D$Z(p}[׮ӻk:Y7^!Qm70T c4*yDJ_KN&8y~zQҩV ͤ͋EAo%LTx2ՒDZMzmE{&n@UրSe8c]6Ymҟ̴ze^'RF+e kv%8IEs+ XRD"$JC2ApNS ijjpF4<a:KQuRø~ 8+`g߰;JgOGYD+Q]G ȫfam+E!N]y4J7gÌQ|]_è,qL//gkęK\ԅŭ aED1]Cn -8GB!yHX@mGSgTsWNgϭ'@7 ~<ŢdXN>26ݔ_ '3|˰"Pso(+C mQ?<CyhmR'*zt:Q0$Ӑ<}qgem~y~#~Lvc{Cr!u)Nŀgxy!0e褑!11/D?@3?|TÃ8"jDs"] eZDRdvY gfD{>qtiS>" G ?2_4V>V;%a$`n.SLx|NaAϤ۩{q.$ 7E9rs(:$G1],w?"0$ȾE~X[I}Y.\M*i;?ύ5-">e b׍[*cFBe\ I]inWXW6A`{.UOʭ, ,Ku)"YTv; @g=AִzqQ̸9 U,ŧn#CQXF59ޝesm18r@x13ob_oכ[mMPHc2<6c񈫺coZ;YuWĿ']SQV@z\{FB<>fUȟxtӁ%Q176vޔL,tSWE" xVKMi9,s=@{![N@%SLw ;]bǢgMZ3Ȫ3ejןt@M~J+ ] eVhahUm`2A>d]Gھр4>w_'>_胬4ۍ.t#9QΊHH$WVOQxh~:,:Oڿ[Q>C޿_ҿ-n-åwæEtܿi_{k~:d?v kC}7[zG*ttj0U+Rf!O|?119TTJ&gg_[_(w0&09y :}Vn֍B]{l/,Lbi\Q:2{mۖT|j9c(]2MmE{.܇j"?K( C-IH:0=3 mL /ULn"X4(]]Dߍ=IړΚJ7M,ۡ[&IZ{QGpj&̵OEYiR iS!Hr@)۸0i ɇЋY=gq1T3Hqt[*$ JC$ |Ɖ#q1JӅ8`RZį_nRBoãe9|Wi۶hIإ9t,oo`+r>m%d<3!lsGv>hX7 y>I.z dUב`^mҍߋq ɟ1Gh_6׋."vK[8n<1 MX'+6]:G%S1 S>ݶBځёzɘeC TfT_ s.?k*lHr Ӷ ` d (dYycd9֛]fX_*vQpE*wdnEUVtX(9>502b6~`''hI>{Z&c$3h0)oM(aKI(L/oZ!X8Ѝ˦֠8tMзLPaO؟1#ٝt$=HZ3:ˁ, r6H0T B!$8İ l~L04t(N 8(`w(l%.s:ϝ*)$.Miaz<@z kٛv^0dXrJ޲ם H. ]lKtPJZ89HQfbIR/)עkG|N\|K} 9(h]Cx,E^KJ o?J^ H#d"9JEQ#A2Wz'Xz9&2 Nu4U"d{2aC3fd :gC9.:ws @Lpxho=f.l.]x9+hD~mզ˅R =xb=Cb ;ja?3}-OrᏏ?cMj2זؒ:?kc#hm)ҤT16 Tzb zrleNXZaĕH1ψƂNZ_k/ e~R<D /Ҿ(JC\0p?!<@erPR)Ӫ-U0"l֣*V,{}ߦSe|D8y$D "$פM]%V}YmiZxE4<soqj|mS(S "%N'$͸^}9w"[ۛI3Hzq ~2_bXa (񆽧MUГp'j{s}|E"%)d ʄ UV ޥ棕{|(c d@M_<K) X/ZXbtr OC=2\0sT}%EVwlLMeiM>] q.71P5i$نOF,lUۂ8 j_`7֑Tb`ȭydU;kk҉k9OR@2 K лh* {Çd.m$6KR]YQ y,=׃_b GbqahP;1V+DĨtw\fYhcT"9Gr:D qglru~0mȋ琝J,lDfO,n j "en[ӉV\QGT25W+#hi K%Yу//ֿ*?Ÿ{f=dڅfôi6c|[/S,s*F$7¦^z HOe:&󡯏hYxQFkkrGZò2wx /rQ{͸UTS_12y1PI}fNi}߂M$AFM+0Qpr1XW@3)"6yEot(FX6Y ѣJ";Z@1 q/\,$:啊w7ae>Bd=? k#z8 'zWaѢ9W⫁=ЯlG{`]'k([ o\E19 JVCɢ nN4bsn <rX>-~lXID2C#9r9Y(-,m\0JS]^kh6DI=uWyfc/Gs莛CQr"~L &!z7LӦٿ$>ٕ$czVHw~ 8 W>v-8")~1x OR$A[h7àB.pÉ;TO7&^qZEK4z%}wbDGȃ6b.s_SCƼS !(]l5 7E'Y:}1ҹ@ؖOűO-. ]:6A*P?jw$N-f2u{x$"l" K՞,to҄k[Fw_-v睹cڲs,|{twX)w> }T>z8 +/617, -%JC@$RęXf_3b9ӺvRb  fܿ[MeaoQ :)qp4ŚVL1ˆ ab}C|=Iк?8 /0SLe4z^;PVW2| $uΆy&M$|߽Մo(waȾ➇$b1,&b >X t,T%P] 9vz{ 9_jg n6O>(8 )0 g=D!|?8冗 q@CRcfE$ c=DbH:z*,Hji-EYOP!w SgOG}%gmC. 57h 80sU$]&$Uq[vU_}`k⾠ZLwX݌cͧ6i(HLDopPWj,a!yދI`F-ֆRنPKo&վ Q la:s%?~0/u2Q~cjQ-!|U`KӵO 碔f 6_oJ #i##58?8kh$~2HG| !,[HOYh^]W_ /jZxxXZd*FKl zzPУ$j2(aG7`:ckD s$:DY1/Kީ粿b1uj@Ue<0:tvn/@p˞|;##)ԽM%5wVe_T#cQ?ƀ2.B]$#*K;'ZX'JU} }68dסq@Zf(HV л2X>054𩬚ʕ1cdǵ/'8F>1O( 67^$.##dǵ/'8zy /- a>1O( 67^.jax_BR_$_7|7|dǵ(6ff4k I .r4*kחt‰[ /q2L Jmh @#} p>0\#!1b;U('ysX;l*fa)9htl򊂒d&i>1O&AB2uу*ę!c)`4y/PLۧۗխݪ=,j. +rF%_>A Hi{}%9AA ۝l01"iO.(ųXNe$H˺x# @Ne$H˺K/G%@B\0P(DN= {s nptidr+\134v k7?Qo"jc@w,Fi͘goC.njѐ5( {C)͕יC/DZ8I1pIP4! ,d+e]hi>G.Go }˰dP/ ͷf?#]8@BN]}{߬UkO,\"]MY `2ܶ)T|},-N:8iQgQH1>>{cX`6)H,ӌ @. SA@)zd| u@nVo}%0j4\nh[r:E)sUJ+Z_sq"@wGlxAJ51cc'cT=a栮 _bͣXS0X[H8<kM#3CEmaW"UnI,D釯 |I]xU-/(馴Z~UK>~ܟcecilia5-5.4.1/Resources/CeciliaFileIcon5.ico000066400000000000000000005364221372272363700207240ustar00rootroot00000000000000f400hl5 ;(>v6?00Zv hr (ڙ`` @@ (B600 %x  z " hPNG  IHDR\rf pHYs+ IDATx]Y#޽nff5'D$ʪ691aZ-$~I{߹/^{m^{m^{m^{m^{m^{m^{m^{m^{m^{m^{m^{m^{m^{m^{m^{m^{m^{m <ܩ3o>`w y3cޢ]ϯYw~磊˺}q>q?%? qt9u_-{c?zLȵJ[uo9ETpOy>~9u_k|O;X׬ks_3^ u zf⨻*GͺMvRiW]?MT Y럹wbnsD7-aK٬RKCWZ"TM^=]&DsyR#'YT" jl|gЊ8;zW:38RP\q38@F M^^FUqw&ոLtE:Q?^WVXШe ;3J2%YTl*@@cl\Xz JPbHU vDYkb&xPWKu}AjF UY銯72 o|"Qa2RĴ%\UƹcT⢈ 7z[3m\iB*o1DytFUQN7zE5[*,a4kD+P㼕Mx cD?= VyFT6Q.$Zd sJ1,PVG"**eeV4̨n*t߳z* V*UkQ+GJT*[x T`U'\&(]'dzYʌBƓȂY{;2޼|U5\4ĩVBYXqs*!>Vx`tUlա.v-ξO{Ut*t,@MB/ &N-)E̵ßV`;jHoT2"rJ@o5ZaK~Z5?>Cׯ\^Hn,,T܄ ; /yzcs5I/yX2L:-x޾--gg'BALG;;9*V\Tu2KY&SQx= 1>Rĭ⨴(?ȑfVc=Z<`?bFtd^^~;NDVNz3,A&,% B/^u[elaN=׏*Ē{@`U6j*^Y]d,/ߊn)`^,9c}q%t({QTI?ygBUiQ t=)a^:LROh X-C^!{ͰA??"LAlHlH/WU V[|:8P IBY Z_GpM/NxL UU2!+;& scjM9Ql{a}԰N[bB: Bގqmj u6B(nRfV7 U7 añ2HO߲%uuaFm@9NQᇢ+k E9h8DuO{ܯ׹6.vH$/~ !RI|}kע VZSnxQ}TyU $8l[x$K-cCd96N9h.j_~@IB""pUf ԳΗ-Ms^L8 I+A,m<oU'.d%WP_c EV.V*Ģ@sa!XK436u֊WA(a׻WUYg͛A*0M/b@!y+1U6ԕӣ(4$([bm܋kHwk1A#V()Nm@%|w19XꖛYuVˇ<XY  OPRqX@; 2")3B*;kAeBqO+A7;O? ʹxAQՇz^"/x~vlov%˪4Շߛ ,(q<Œ ^~Z=n5b}7X+x9w>W HP=I> T1#sdS,|l%`d,z[8ڙ=Ȗ8+g&fp BӊoxLFC1hv_hOu(r8`0-p{@|.7Ng7dt]w {h&p^LO Z.{AWoZ;/ -:b]& s 0Ʉ_x+=+ށ9@*V V X6L8(Sg=eU[{q eHB#)waNe\e4+yzw}d0B j*=c 򰬡!8Xi,]/<^t86%jBG[R#ťĭ&y]Hm{-zk{2qGP͌xhXnN! w@hjiE9c9Ks+~]Tdb р)=u2bSDRY}T^e%4kUGZF}=3{Cz) 0*HJ=U2\0ϳ6|+rHɕKAS/^z}~"ky}Z0J%L8oV#UlWim@Rϑ7J[%uY  S`$ܝ03>~!mV R6*f{e:3W٧rf_z0Si"V_v˵w,/'Yi|LhY/E{zDU6?Htp ch`P"eY/|@j(bhB=%lCRzssN|nr-uY7]??ztP3@<VMHtX[0Ey(ț {qnBIpB NnN@GkN0{Jz )!za LT^Sa<6 $ g!\rj|V~+j<79>ӣ!$i 8{VrAJ}퉇&,6"|☐]$dP57#֩K~}idW9q ",eJ9u{(RPn>שzƊ͟҂#F+#"\z%? ][i wVO;g@d$*\aq=A+F4-7ǼqB'䘅/}^RI7H\mo iX%oG(tJ鼨KJ5|v `B=~H ||h|!f"M4 o n.:!8lQi<y૗[уTaLv0w' \ۡIfj0cLߥ٧,TYC4P-Fki5q *yOfbbT\R{LKX}[eњ8*<>лkyǎ~(bM=RdFE8~״80Gyi?2 *E,* hxA3e a<5H;4$ϥ" !C(xQJI<\X/\0Cfa^:s:Q'0LF 9Zk+- FMSQ]]}~&BԥV\VH I B*2oh' %Z{ OMXy TyY4dɰׄ׊Bm*pG)SeȒ<Dj4CiN!`~?2&VUGt{M>#gÛBCt>9Teox~D,e͢^.ۏWPT) h:Fsc<iEF g^jmB0D&IB+A䫽³:KF={J$J1LhTq{=^C~]WT< .w;r./URxb4gY8ÍtXjb  /Ra]K=K9.gD<o;%}@O !<1O;⭎12tU,u(<O,m0O쏦ASoRPj1?Zz/da1]rv0zמ3" #Ho9 WeB#@L_T{>C4IkӣNo\Mz%pѲ^y,?,-~@hs )=_2ʀ,l0`Dʖ=Qp7^JHb՜ '4Jx`6Q,OmJK)(33А\sw|trFފ |OiQHkfed@9o'5CA/ @>NEgaYف1@\ܶt7-IhAe uMOJPs[uDc>~͚qzg"yLt-~e*դX?:v`Uz<0.@`Hlc nXj3}o8PⱰ ='8Hd!"2ˁ-l@/!92cʢ9hMRa ؓX~Ò~=kMVji*\o7G G{IWXJۤH5He63Ssa#={ma qd/'k 9 pzo-=0X>Šq515"W/vwQ=)nFhWc"Y)YnH 29߉es9v@$30c+(0mx 9|'v<'XD%kz(O rV^Ny^9|p0Ϸ8QtkӛD x|UP^èͮjͭ'샫!D)-6acu-i10rLs& .^CWx> TCYi>M6K \\&  y[VÔb'\MAǪgǖe^Bu5*\]3PG޸ jijj1}^&6*s^<hh z<2L cO&=-  H9*. KgRaCQv3JAOpAr̔|s!- ]J8RY͠ aa "|4˨!mdySWE2ػ;}/7r,Ku.' Sp86>V&( ҄pPIPG~wGz/#m=,aA!d. jNi`{TJm=CIB^z.83 [jx9X-MH__ :F62DkZh_[?pw Z=2hH a uB6+.=7/E =ɋ[ݺ{Rjq404^A|,i P I8R\|˳< EZ`*DT|,yğ16s,Pd=۰y+ a 4 ¬jRkeHh nXrLf(28 *F#M>1{},@$jM]=Ta(›02ayLxdBli1BӊBZ$=PPv zjaʏ_)H,m4ˋީ $O`,){^m!"=$ ~V/ :#De!Te3 O`J`Sְz0kGg~4-D Q$xYH -QfV\3}mzL?`o oY2zBt;Ј++^ qE8 Ϭnx 9v@2@ݥD`F2!:,~׍H 1#IbB3} 鮌)y=^?jfH3~B ) )w=W`cp!#5W6Rl9X{'bَO)4I4`T1|t!PfUV|VLBa Fz+PP܊j=/@@ǩ=e9C 4XUjdJYꯑ;!W헰"c|p#Ώ׏ǚ?g1 3Dr}YyP t2r\/Ӓbǐz`4`!%QxxD `78$O<܂(oUnIc¥vJ=MѴ=,( M9bT f C@CrE%`\h~W ->j4EgT7\I]pq\80#XP kcy g4€wֿ۞i>j QHp;%S.epvk} weT?(b-X ̰j^@2q-z8$vr06wGayn ,i'Jyzڋ*EuG{ T w6ڵc,ANH.q:k?v :M`T~Y j!vyҸe7`s`iWXV`.5faf#Gcc ^M3`*:~b#5ՙzsGT7 zS_L@yB,ʞsa̘f4E%(My._,~Ed-j)v%iN8H2tt>;pb^)rKIޛE.tT!{h7`ls\J |$Sa/9 Dg!ZfrdT[!0ku9I׏jBY kƱ* +,(^vϞIckc1 vj|%p}zq''u!(?8c_UkYQ "Aa0TKrwKr=BÁ6 $|bD5h;`k?n oA() /`RL\oRg;h? fhI$>%#lf @26###GgҤ0[PӫQh,"ώMd˯cNu) /@trHG҇΢̸٢eB뽗~t A[n% QҐ܄@K( ioAV d3%)nvxJC M=HYX @" RtoEH-ҀӰ$engnym/ [?ܢo\M +!#`$MXK@~}.v TTig0~CdkM3kq=/ApDdTEoq[xA?>Ʋʵ˰RX% ^ZJꄧ55+0W"M o g)ӆ.q}rڭ4젻@J Eyx{ J;t{=O̔ΫJ;D/U$z(T;]}/ Bv kf g01\Qhw9xq]hT%X !B#D(3_IU5xƽv zkz#lElw.,(nUKx {A#DҬՓ[<@z G"@9һ}WE\@‚* w*d*؋Wh\r1B=K_pgϢeܺôr1 Gf! gWX?7ZΑf2Tz2E=^AVzbMo [hk ˄d  Y~<{wKCQf~` - RMȊ̠ӄ qc]\ K@`!L޵4t_l~9~ @(u"B[P`Б* ֐r8qUZUIU'pE G.aX6nv,F{Dd;2B]I1B)z~[xs+"BGAY , 'c;L 4,tSq ge)+BWXU7u=k"V))HzjV,Z}lƌ,؇d3!g-i}/W!˶ދ*TXV)-fE ;:/Tdďe& AI+fVD*wi2KQeiix:31j@D3@23/PN DO(sEy (W{'F /V^m:oHZO(:幗\*I ֨*|ꯙ -%F2k0(-sdDw,OIf{=T[x"RQ@p$,H9/'xbYݢ2R}˿enD8ͣY"@jJ "̢6i?Jx,cU[V%SǏKN~tg9+W_Q5lLط*J=@IfXC4 J0"2dQF5.f6Qrm1"<.'&+tħVn*g oTL[GK#76F1lQR,>UfEln78 1z o =Ontm "w`b>?A}ـѸ*{*Dh`^{ebz~aZk9 #/NdG% lnm}Yؿ<Yfx?cB(^^{SV20+OH nÏrc%v`pw/W"kRߪ"k?kZۗ`kkkkkkkkkkkkk>|? &.nIENDB`(0`wwwwwwwwwwxwwxwwwxwwhhwwwwwxwwwwwxhwpwwxww~wwwwx~ww|hwwwwveFggwxhww~w|tacaaFwwwwxwwvagwww!g~wwxwvGwwwwdwww|xwwvBRwwwwwGwwwt4'ww~wvwwwhhwgGwwwwwwwwwwwwB`wxwwGg~wxpwwwe6wwwwxwvwwwwxwwwBAgwvWwww爈e&w~wwwgwwwwwxpxww`pwwwVwwwwwxvwt'Gw|wgghww|v'xwwwwww爏pwvwBW|~wwwwwwhxwBFwwwxaw~wwwwwgww|6wwxxxxxhwdv|r@wwgxvwwwpGBCgxww|wxwwwwwwxxxwxwwwwww~xgxpwwwwwwwwwxhhhhhhhwwpwxwwwwwwȈwwwwwwxwww|?( @wwwwwwwxwwwwxhvxwwwwwwww~|vehgxwgGwFwwwwt'wwpw|wwd7wwwgxvxagw~wwwwxwBwwwwH~wxw%w~wgwwxwvRwwxwwwwxwFw|vwwwxh0gwxwwwwxwwtwwwtw~wwvw~Vww|hwuda`wwwwwwww|xwwww~Xwwwwxwvxx( wwxwwgwvgvwxtwwwxwvwwxtwwwvwvwxwegwwwwwwwxPNG  IHDR\rf pHYs+ IDATxW.\@8xJNۉ{ι9gkݿq3^}Ύ3lx`2FP J|/,K5~(N'Xr  @@@@!B @@@@!B60_X,0MN&2Ʌ8%(,044DLwwFhkk  8el+8tt/[`ii SHg9W^>REpd訹 e#w`XXXvR==G~Gs=@g4ox|X,btt+8:Q|嗔 _H@p躎1k`vn\]]Eg4[nG@pH0 [n9I¹99Bu&۷oţO144>p]S4\r?A駿Ï?ݻwzu#utF&wpELNN!.077gAr~x cĄy50!#F?~/q5w(bhǐ v#tOi! 8O@pLaB @@@@e$!J ӄ"r 8:E,BEAHʼ܈U loo#N#߆$I0 |KaTՒ%Y(d}k~҂H$&Atww@ฌ="J!H ~Xe*mby ~gv"E*B7iH$WjSD+An!"L"J!ccc%h0r;PZJ]5$r(y.ΜiG?(B嗖Ǒm`@!<1E %JyhRK+Rh $d @P ]בH$jû _Ba [ 23EK === nr9c:)b@4ځׯ"!Ɣsssxl,4݀/ԅH_;3 +T|* z;1446dYF,ógHe 'Ra ~g<~W^ŋ"=8_x:,G|aR+&'E@XXX4VדD\ \AUlEg7I;L&1>>:DGDי?݉,$E@b#$R`pp7n089l2FIz918Bf S3X[['B'ưQ=\;Opv!?x<LDxvT!$@t^Yylll*5@S&!,G,`y%ݢZP\?D"?\B_^Y' =,,nݺE!JuҞ@9ҫdn!!c||?(`n^w=@ B]v%pQ,-XKt\j{>J ݿl67nޏbQC`F6^ s[>͡P(ۄt?32[Yϯ?QWxp(àˈ믿"ABw /z哀 %d;% PkRYB*X'~I@; FtX/ݗNw^A{NTIP4"#H/cttn:ˋO51,cK /ɸ=JIয়~>J=hAR9Oe$pwTZkIzyqGMSuKf}U'+zvF/!(S5eT ^Q^rI{51m+_|A0>>¢g{1k{I7DBw?hii95 N,//c#<ۿ_rR3[H Q*qhiiA__!#N_ p!:2&'Z(Obt. 44Ȳ1KE4u^n't^Eziccco x9_O$l+ZfA0z +i =ca `aa3}m\{`δ#Aӌ$A(6azf=== hHe>tbCg rIHr J,K%@6PC՜@4>J!9 Mh/rqR5EQ/m $Ԑ066|@gbS&@*@*FjZYn`"̒ I-a^MJR-H[3 6(B{"YD-EsSǙn5F> zɗxiC G˘- +e2I,_XYFEP8V~\* K%A?R eW_mNBa4D@KË}4l(P 2 ^^_}8,xNa$-ʊCBCu8§z57LPC2`y04&_<\,$v~!Q00arh?tMPU %`೾7M WMæikN 7„z@m,//7DB: yOQlmm8M:;;4{ _ˠ~bsZ#QF""\$+q UVɿ|Ta! <&'$fff ۓwA ڕq܇vtI*0 M#i "[ '>:e'ЭPJ^}!LOO8Nigۡ(':o(1E1>P]GsE ]2 vW#x{ ]լ`5݀P x`ŔL?#!KK`pKxh=+CG9߮Hr2]>纍d`,#peVV.OY_ 5Θ7ޚt;}h !wṮLɏÈ$8  K(,j~{5~[g{3 oOs(m8wVWWMH/x8'wP( r>'{ͪJ5AS >||eL> lPt* CCv;; X__'PoTX]]jZS1q~lՍMza3k*r5$ X\\PO~ǩ CC̹L pʞ 0=j]CTvv5 ybgaO"(l,>Av+U %?Ke"v}XRf:0aP9a@<'P/$IxȞ`V7O4tG%,.BcDy_HxMA'Yk?8raT hPAnWO Ành:w\9=s{lZ5@h&Njmqji#~bNS`hm6lGK{p ӛOԨxy]'D"`G= 5lƝ?]G Ǣ{n^=iڎB% R)B be~YjA`{u?ywTjb,f$8,667acν<\1(P%W^}$`i #( 4Mcmma 2 G<õ\޹ 7uai&G-0Fp?^' :׊!àP(uJjNJf.Ɵ״2w gZݨq YSPKoLq Nʒfz4(n@=L\4};V"۞*v6nWEѠYA5w@ LYkT_IР_D"k653e& ;!R%n0As48A8 S3ah:xV+X3`8|PC׼緉8(6S#m> 7=>I(s;xE 0U(w(ӥNSh$e{: p:V Ԛ p}{/(НMB>c%_RgLrEŸ'\jzwޯ+.4r,㴁}Phz6k @$KRKMALIaV_i5+{ەB`4BEFB`4 aSZ7aTk/0 `> ܮXT

c٭M" @}Iþn\.8hMA) a`8&MwJ@d){iy!ж%E芜24|>!FL+A p̡PuNZ N5(32x VXF6*Yd׳XڈӋ"7A>g&9+K"+Y}MBBh #lx:)%.~mO{<]YW[\;{b0C]< @0YzMPT<8i%g|! 7)t,# ;8 BUY-}fUr(`3޷I5zvp 2 M'rNuճ$xcģT%T)Wwѽ\&pJE(Gi c>h%@1T I.y8s<]B!6YG=d !"4uc~<8$``&m퀪(Ng2(!p= Ju0  *6~>G^?0EKm6 P_O "8,$ާUT˲H$ecƺ\b [I@18 %LC'*Yx9֮ylR)LsƆ\tOY48,X=SU@ f? (?TZg0*3VLH;yj)Xkn 8WߛkvPAF+v5|P(`yYkھǃWW= [{I tw gH(s(EHZ`0G)6ASRh ` C}^P B؞ L5P@ʐIDATa\˲D" A λzt kM~IVa)Hr뛯0p]sU8Mw4d]uLgz,$0`Wo@j{ա"Wr HJ 9tu^&P<@laUFsS/B4+ )E@*" &:ޮ fum ܭ%VSq\p)V s c=Pl!/@ii?cc%|.b~i zŀΘ I\«}M\kaYP<]$|i \Oora *C?+Os, U Mv gδ#F>|zҶuJ{%*@Y.c5*`?JOcm%@k߆?t_Lspub**տÃɻ i*V֠2_@!e144D(7"꫗` 9 L1W/t=z_eyL%9֚`8"o!"8ۋYWn!WB>"23I}DP] V+r)Nf@P5~ BM&[ ajz}a@s_N(P=^IiKr'Oܧb)qbQ9ߞj { P)9À G~;0*`\F_{KxY~pJ5힒#8?8G\*{op 9>l̓ct n 0LKu*Pu"( 9_tbna`$6OT4Aʕ+x:1\p%/>G}*$`{8}jP nw}=lmY~grXpV۴N9P ʅ"p1bJÕ` W/~G~u\ K'P@mj+W'_.LcjWV80P ɕ"A/%p~p1. Boyp~< @@I*wW\˅iLN݇NN}'% =B4<~O@$Ag4Dr zP^B!K@Gt`B|Re^=F6cEc;)Ӝx2ۥ_?a{tTKNJjɑr*8 \rcC6r sM{3P I wa!: n\P{ϱEeܛbfe06|{}읽{ 5`I`xxϞb3,rK{~~ SGa;g]$=x%AwY\#R1~zz\ޑaq,t~Bakh ɫa>~wlҕ-O_?x<5u l5Bsv 0,VVŇChi﫟˥00xj ZAĚvԮvLBKYiyP0<<ɩ)c-tÀ.kzuM;i!ʰw/`=DwGζuYFcti2R$2YjY7&7S10 Upups0 5tFx೛7qO]~tuoXItzwVP0;1:eS5+f`~y!lYPh6U( Hr jmW@+Ȏ ,@ r~:L479@p뜧lj022ׇhuMm+MѸz&cb7ldVkXpbMo2!ü87B`ss W51M&;78|F}g֚{+t)(&Ρp !?kB.ϰ^ʯ:daaU@R6@S硳&Xn}f82|Ƭ1~c۞_5~c ,Cy߰Dp5e!jWE0e^4hVcip L2Y|`pi>ۣrAIri~Ə}pU^V{믡 V "\DY݊@K7qM%xJrzIA0 ,c^h6"0tM9f% 9`E&c'uv=ADk4u]@{['ZlQYʕF2"Apu|;mk+VKa%aMUU4TUr p~nm9n:S I0lW5}*4?`𳽻]~*P 3m8⩤P%X-^/54VB%L̢zޮ:nKa-62N፡i48JeUx\C0C(Q&" CGDM:<1x( \|!~ O5; Epbȝ UT&B)USe+nAVJLT]]%+mՆo/ahE0O{VGKEB) D"@q+ {O6oB]2J l ,P tS): ;W_5ohI˘E)yMɁoC ;[+" /HlB*f!݉5? nܸNE(@pCss7nhe#V 6֮vfCKSCU~NL ?ցVrPsyVCN-\."JY;K U>5͞|-H (Vh6>?[AoO>SuA۷waem ri[K۷OA[066EМ@ʃR1 )|vf6؁H$[n;wO΁j -2 "!!éA_Fa+5I( ȭ!믾: &ӟ,H")"1~B$;%f`DIb5܂? Ġj)F; 5g+[n'& Ձ B :QbM| ~W*i0 CGn3~?é,fqL"VFS9ƟYpe'w!NL`{m~R! YE>'PGܹs?.pIHDZ#쳛 ! ӟ;y4 "^J7n >B#o+Q(oOJ.]7M#|Ұ| 0>s YMC[ ؒ;H^?$OC??all El75p(dPZK$Ox!K\v@BS[/ 1d e5AbNN LMMa>C>1r͑ Ղ:R. PsIO%j.޿^mB viϖ$Ojo2>}䦵iGǷ*wvQ݄<>affX le"؛5@oW;_~jth cxxM"6Y*^D;188Hx 4ءA"&L6@;0]˅,rz)b݉˗/O1Ct:ObmmJMh:4MR82a(\|CCC$'؈D"/!2D2|,52P)$u|B vpddtŐ˭6_œd J9'ǹs爷'@`H$R6S(k  B7bWaP j)vH|B{!{t:BX,MuRz/kq)8JrNޙ MWOMQ8~h{BUvF%9@&E)@F*@B-BE-AH*BJ,EI.DL-DE0GK1IJ3KM5LM5OM9UR=VV>UX<YTB\XC]\C_ZF_\E`]FaZHd^Jf]MgbKhaNidMmcRmeQojRqhUulWuiYvlYym[zn\}p_s`rbubudufwhyizl|m~otptvqruuvv~xxUaccccccccccccccccccccccccccccccccccccaU]eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee]WeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeWWeeefeffffffffffffeccccefffffffffffffffefWWfeffffffefeffeaRG<<===9GR]fffeffeffeffffWWeffeeffefefeWE=ABBBBBBBBBBEWeffeffeffeef\WfeffffffefaEBBBBBBBBBBBBBBBAE`efeffefffeWWffffeffffV=BBBBBBBBBBBBBBBBBB=UeffffffefWWfeffffffOAABBBBBBBBBBBBBBBBBBBAOeffeffff\WffefeeeRAAABBBBBBBBBD)@D+BF-AH+BJ,EK/FL/GJ2HK2MO6RQ;WV>YV@ZXA]YDa_Fb[Hb^Hd^Jg_Mc`HeaJh`MjdOpeUriVthXwlYxl[zn]}p^rasbtbuewhzk|m~oprtvvxz|xy~CFFFFFFFFFFFFFFFFFFFFFFFFC?HHHHHHHHHHHHHHHHHHHHHHHHHH??HHHHHHHHHHC>=<>CHHHHHHHHHH??HHHHHHHF;41421420;FHHHHHHH??HHHHHH>014222222141>HHHHHH??HHHHH9214222222222129HHHHH??HHHH91112222222222422:HHHH??HHH?211422#)2222,?HHH??HHH01142$   )22214HHH??HH>1142$2222$22242>HH??HH61422*22222222226HH??HH,122222222$&12222,HH??HC142222222$$222222CH??HC2222#222222#222222CH??HC2222#22222)2222222CH??HF,22222222)22222,FHB?HH32222 2222222222223HH??HH91422#2222*)222229HHB?HHC12222 -2)22222,CHH??HHH822222$ 222228HHHB?HHHF42222222*)*2222224FHHH??HHHHC422222222222222,CHHHHB?HHHHHC52222222222224EHHHHH??HHHHHHG:,22222222,;GHHHHHHB?HHHHHHHHF=642,46>FHGHHHHHH??HHHHHHHHHHHHHHHHHHHHHHHHHHB?HHHHHHHHHHHHHHHHHHHHHHHHHH?CCCCCCCCCCCCCCCCCCCCCCCCCE(0@@G*AG,BJ,DH.FI0KM4LN5OR7RR:UU<ZWAZYA`\Gb_Hd^JibNldQoiRqhUvkZvlYyn[zm\~q`ubveyi{k}nqtwvxy~2222222222222222228888888888888888888888888882,((,2888888888888/&""""""&/888888888(""""##"##"(8888888("#"###"#"#"#(888882"#"!  !"#"28888'""! """"#'8885""# ######""588/!####""!"#"/88-#"#"#######-88-""#"#"!#"##-880""# #"""!##"0888 #"!#"# ""#"8888(#"! !##"(88885##"!! """&5888882#"#""##""#"#188888882&!####"#"&28888888887+& ""!&,7888888888888852258888888858888888888888888885( BJ,DK-JN3KM4LO5RR:XW@f`KleQvlXzn\}q_~q`satdvgyi}moqstvxy%))))))))))))%%)))(')))(()*!!)*('*! !)('(  (&&! !(& %&(%"  "'() )'%*%#)(()*$#**'')***#!!#)***'%((''''''('''&( uutqqpBkkjmmmoonoooppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoppoooooonnnmllkrrq?wwvrrq3ffettt󘘘tttffesss1nnmOjjijjioonMppo3jjiﶶjjitts1ssrffeƣggfzzyqqp?ttttttuut?kkjmmlmmmooooonqqpoooqqpppoqqqppoqqqppo~tokwnitlxn}qstvwwvts}qwntlwoiuplqqqppotok~rk{pvyyyyyyyyyyyyyyyyyyv{p}rktnkqqqppotnktltxyyyyyyyyyyyyyyyyyyyyyyyyyttltolqqqppo}|xoi}qxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx}qxoi~}qqqppo}{z}qjuyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt{pi~}{qqqppozpityyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytxoiqqqppotnj}qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx}qtnjqqqppo}|tlwyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywsk}|qqqppotol~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rtolqqqppoxoivyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvxoiqqqpposkxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxsjqqqppowmxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvmqqqppo~ynyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxnqqqppoynyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyynqqqppowmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvmqqqpposkxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxsjqqqppoxoixyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxwniqqqppotolvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvtolqqqppo~}{ryyyyyyyyyyyyyyyyyyyyyyyyyyyyvwg|n^mdQd^KZVBTS=RQ;RQ:TS<[WCe^KofTrb|mxyyyyyyyyyyyyyyyyyyyyyyyyyy~r}|qqqppotlyyyyyyyyyyyyyyyyyyyyyyyyywteh`MNN8=B)=C);B'E)@G*@H+@H*@G*?F)=D(E)?E*FI0`[G|o^syyyyyyyyyyyyyyyyyyyywtnjqqqppo}ryyyyyyyyyyyyyyyyyyyyyywhVR?;@';B'>F)@H*BJ+BJ,BJ+AH+>E);B'7>%4;"49"6:%6:%49#17 39"6<#9?%ncTqbufvhsdyk]cZJDC115!28!8>%=D(@H*BJ+CK,CK,AH+=D)vfyyyyyyyyyyyyyyyyyyyyyyoiqqqppo|zxuyyyyyyyyyyyyyyyyyyyyrcCD/:@&>F)AI+CK,CK,CK-CK,AI+=D(7>%?A,ncSsyyyyyyyyyvhTO?05 4:";B'?G*BJ+CK,BJ,>F)f_Lyyyyyyyyyyyyyyyyyyyyyu}{zqqqppo}rjyyyyyyyyyyyyyyyyyyyyrgV9>'F)9?&vjZyyyyyyyyyyyyyyyyyyyyzl68%5;#=E(AI+>E)haNyyyyyyyyyyyyyyyyyyyyyy}rqqqppotnkxyyyyyyyyyyyyyyyyy}o%?F*BJ,CK-DL-DL-DL-CK,BJ+=D(GH2uyyyyyyyyyyyyyyyyyyyyyyxKH728!F)HJ3yyyyyyyyyyyyyyyyyyyyyyytlqqqppotyyyyyyyyyyyyyyyyyyk\5;#=D(BI+CK,DL-DL-DL-DL-CK,@G*;A'{n^yyyyyyyyyyyyyyyyyyyyyyyyug04 7>%%;?(yjyyyyyyyyyyyyyyyyyyyyyyytnjqqqppo~skyyyyyyyyyyyyyyyyyvjZ5:#=D(BJ+CK,DL-DL-DL-DL-CK,AH+;B'zm]yyyyyyyyyyyyyyyyyyyyyyyyyy~oa,0/505 sdyyyyyyyyyyyyyyyyyyyyyyy~rkqqqppo|pyyyyyyyyyyyyyyyyyLI77>%?G*CK,DL-DL-DL-DL-DL-CK,?F)CF.vyyyyyyyyyyyyyyyyyyyyyyyyyyxQK=@>.`WHwyyyyyyyyyyyyyyyyyyyyyyy{pqqqppo}|vyyyyyyyyyyyyyyyy{m49#;B'AI+CK,DL-DL-DL-DL-DL-BJ+=D(a[Hyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyv~qqqppotnkyyyyyyyyyyyyyyyyyncS4:"=E(BJ,DL-DL-DL-DL-DL-CK,AI+C*tyyyyyyyyyyyyyyyyyyyyyyyx~pae_JTTE)a[Hyyyyyyyyyyyyyyyyyyyyyyyytf;@(DH/}oyy~pIM3KT2fbKyyyyyyyyyyyyyyyyyyyyyyyxnqqqppo}ryyyyyyyyyyyyyyyyqb27!D*vfyyyyyyyyyyyyyyyyyyyyyyyyyjbOEM.sjWyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuqqqppotyyyyyyyyyyyyyyyysd26!:A&AI+CK,DL-DL-DL-DL-DL-BJ,F)kcOyyyyyyyyyyyyyyyyyyyyyyyyyoeTEN._^DtlVqjUpiSpiTqiUrbyyyyyyyyyyyyyyyyyyyyyyyxnqqqppoumyyyyyyyyyyyyyyyyyVP@39"=D(BJ,DL-DL-DL-DL-DL-CK,?G*ZWAyyyyyyyyyyyyyyyyyyyyyyyyypdU@G*IR1LU3LU2KT2KT2IR1^[EyyyyyyyyyyyyyyyyyyyyyyytlqqqppozpjyyyyyyyyyyyyyyyyytgX/5:A&AI+CK,DL-DL-DL-DL-CK,AH+EH0yyyyyyyyyyyyyyyyyyyyyyyyywi[>@,FH1KL5LM6NO7PQ9BH-SRE)rhVyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyv~}qqqppo|qyyyyyyyyyyyyyyyyy}n`/4 8?%@G*CK,DL-DL-DL-DL-CK,@H*PP9yyyyyyyyyyyyyyyyyyyyyyyyyyxvhzkyyyyyyyyyyyyyyyyyyyyyyyyyy|pqqqpposkyyyyyyyyyyyyyyyyyxDB139!$?F)BJ,CK-DL-DL-DL-CK,?G*_[FyyyyyyyyyyyyyyyyyyyyyyyyyLJ76=$9@%SQ%>E)BJ+AI+&=D(AI+CK,AI+?D*wgyyyyyyyyyyyyyyyyyyyyyyu}|zqqqppo}rjyyyyyyyyyyyyyyyyyyyyylaR04!28!9@%>E)AI+BJ,CK,BJ,AH+?F*b]Hqyyyyyyyyyyyys_YG:@&=E(AI+CK,CK-AI+?E+|myyyyyyyyyyyyyyyyyyyyyy}rjqqqppo{ywuyyyyyyyyyyyyyyyyyyyyyrd==,/55<#;B'>F)AI+BJ+BJ,AI+?F*EI/kcP}nyyyyyyyy~pmdRCE.;B'?F*BI+CK,DL-CK,AH+>C*tyyyyyyyyyyyyyyyyyyyyyu{yxqqqppo{piyyyyyyyyyyyyyyyyyyyyyytcZK25#06 6<#:A&=E(@G*AI+AI+@H*>F)?E*OO8e^KrhVym\wk[pfTd^JOO9=C*;C'>F)AH+BJ+BJ,BJ,BJ+AI+>E)8>%yyyyyyyyyyyyyyyyyyyyyyzpiqqqppo~ryyyyyyyyyyyyyyyyyyyyyyy|n\UE46$054:"8>%;B'=D(?F)?G*?F*>F)>E)=D(E)?F)@G*@H*@H*@G*?F)=E)%6<#4:"49"48#AA/TN>yyyyyyyyyyyyyyyyyyyywtnjqqqppoulyyyyyyyyyyyyyyyyyyyyyyyyyyy{mqeVUO?==,04 27"06 28!39!3:"4:"4:"4:"39"28!28"59$37#ED1XRBlbRqbsyyyyyyyyyyyyyyyyyyyyyytlqqqppo|zysyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysseyk]nbSf]Mc[Jc[Jc[Jf]MmbSwi[qbzlxyyyyyyyyyyyyyyyyyyyyyyyyyys~{zqqqppotnkwyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvuolqqqppoyoixyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyoiqqqppotkxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyskqqqppoxnyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywmqqqppozoyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzoqqqppo}|zoyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyo~}qqqppoxnyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxnqqqppotkxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxskqqqppoypiwyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvyoiqqqppotoksyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyystokqqqppo}{yulxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxul}|zqqqppotni~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rtnjqqqppo{qjuyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuzpiqqqppo{yx}rkuyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyu|qj|zyqqqppo|zyzpj~rxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx~rzpj~|{qqqpposmjultyyyyyyyyyyyyyyyyyyyyyyyyyytultnjqqqppotnjtk|qwyyyyyyyyyyyyyyyyyyv|qsksmjqqqppo~|{snjypjumyn~rtuxyyxvs~rynumypjtnk}|qqqppo~~qqqppoqqqoooqqpoonqqpnnmppollknnmrrq?ttttttvvuAuutffeǣggf|||qqp4jjiﶶjjitts4oonRjjijjipppPrrq5eedttt󘘘tttggfvvu3wwwttt?mmloooqqpqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqqppponnmvvu>zzy(` qqpnnmNppossrttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsttsqqpoonNssrppoIppoٙpporrqGnnmTqqpQqqpuuuuuuvvvlllxoonxmmmoonssrttsssrtttssrtttssr{vvownxozp|q|q{pxownvo{wtttssrwqxotxyyyyyyyyyyyyxtxovquutssryuzpwyxxyxxyxxyxxyxxyxxxywzpzutttssrvpsxxxyxxyxxyxxyxxyxxyxxxyxxxsvptttssrwqtyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytwquutssr{pxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyx{ptttssrvpvyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxvvptttssrwnxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxwnuutssr|qyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxx|qtttssr~sxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxy~rtttssrsyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysuutssr}ryxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxx}qtttssryoxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxxyotttssrunxyyyyyyyyyyyyyyyyyyyyyyxutsstwyyyyyyyyyyyyyyyyyyyyyxunuutssr}ywxxyxxyxxyxyxxyxxyxx}nwkZc]JTR=HJ2BF-=C);B'E)=D(E)?F)?F)>E*FJ1^ZEym\ryxxyxxyxxyxxyxx~rtttssrvoxyyyyyyyyyyyyyyy~pa[H>B*=D(@H*BJ+BJ+AH+=E(8?%6<$:=(?@,>?+7:%38"5;#:A&>F)AI+BJ+AI+>F)FI1vyyyyyyyyyyyyyyxvouutssrvxyxxyxxyxxyxyxxthX?C+=D(AI+CK,CK,BJ,?F)9@&CD/kbQxjwxxvwhg]N=>+4:";B'AH+CK,CK,>E)qaxxyxxyxxyxxyxxyvtttssrvnyyyyyyyyyyyyyyv]XE:A&@G*BJ,CK,CK,BJ,>E);@(mcRvyyyyyyyytaYI27!9@&@H*CK,@H*b]HyyyyyyyyyyyyyyyyumuutssrvxxyxxyxxyxxyxvTQ>;A'AH*CK,CK,CK,BJ,>E)AD-vgxyxxyxxyxxyxvi[48#:A&AI+BI+IL3xxyxxyxxyxxyxxyxvtttssrvoyxxyxxyxxyxxyxZUB:A&AH+CK,CK,CK,CK,?G*@D,yjxxyxxyxxyxxyxxsfX27!E)tcyyyyyyyyyyyyyyyysuutssr~zxyxxyxxyxxyxxyk:>'>F)CK,CK,DL-CK,CK,=E(`ZGxyxxyxxyxxyxxyxxxr:<(:@&=D(siWxyxxyxxyxxyxxyxxx{tttssrvnxyxxyxxyxxyxxSO=;A'BJ+CK,CK,DL-CK,AI+@D+qxyxxyxxyxxyxxyxxxyi_P39":A&e^Kxyxxyxxyxxyxxyxxyvntttssrsyyyyyyyyyyyyug6<$?G*CK,DL-DL-DL-CK,>F)e^Kyyyyyyyyyyyyyyyyyyys46$16 VQ?yyyyyyyyyyyyyyyyysuutssrxxyxxyxxyxxyx[UC:@&BI+CK,CK,CK,DL-BJ,=C)zkxxyxxyxxyxxyxxyxxxyxqeWZRCvhxyxxyxxyxxyxxyxxyxtttssrysxxyxxyxxyxxyu;>)=E(CK,DL-CK,CK,CK-AH+LM6xxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxyttttssrumyyyyyyyyyyyyrc5;#@G*CK,DL-DL-DL-CK,?F*f`LyyyyyyyyyyyyyyyyyyykpgTa]G[YCicOzkyyyyyyyyyyyyyyyyyyumuutssrzpxxyxxyxxyxxyi`O7>$AI+CK,DL-CK,CK,CK,>E)|o_yxxyxxyxxyxxyxxyxxDG0EL.hbMsiWRR:IP2sayxxyxxyxxyxxyxxyxyotttssr~rxxyxxyxxyxxyWR@9@&BJ+CK,DL-CK,CK,CK,=D){lyxxyxxyxxyxxyxxyxxBC0b\Jxx~pIO2TX;xxxyxxyxxyxxyxxyx}rtttssruyyyyyyyyyyyyKI6;B'BJ,DL-DL-DL-DL-BJ,AF-syyyyyyyyyyyyyyyyyytwyyxQT9OW5}nyyyyyyyyyyyyyyyytuutssrvxxyxxyxxyxxyBB/;C'CJ,CK,DL-CK,CK,BJ+CG.xyxxyxxyxxyxxyxxyxxxyxxxRV9OW5}nxxyxxyxxyxxyxxyxvtttssrxxxyxxyxxyxxy=?+E){lyxxyxxyxxyxxyxxyxxzm]FN/fdKifLgdKhdLsjWyxxyxxyxxyxxyxxyx{ptttssrxoxxyxxyxxyxxyzl27"=D(CK,DL-CK,CK,CK,?F*{n]yxxyxxyxxyxxyxxyxx{m^>D*GN/GO0GN/GO/STF)CK,CK,DL-CK,AI+\YCxxyxxyxxyxxyxxyxxxr8<&8>%e^Kxxyxxyxxyxxyxxyxxyzptttssrwqyyyyyyyyyyyyys<=*7>%@H*CK,DL-DL-CK,@F+xhyyyyyyyyyyyyyyyyyrfV8>%C*?F)BJ,BJ,?E+|nxxyxxyxxyxxyxxyx}rtttssrxsxxyxxyxxyxxyxyxxyk\79&4:";B'@G*BJ+BJ+@H+GK1qhUsxyxxyxwhZVBD*txxyxxyxxyxxyxxyxxstttssr{qyyyyyyyyyyyyyyyypZSC37"5;#:A&>E)@H*AH+?F*@F+SR;c]IibOe_K[XCGJ2=C(?F)AI+BJ+BJ+AH+?F)8>%yyyyyyyyyyyyyyyy{puutssr~{wyxxyxxyxxyxyxxyxxyk]UF8:'28!6=$:@&E(>E)>F)>E)=D(;C'9@&7=$5:#47#yxxyxxyxxyxxyxxw|tttssrvnyxxyxxyxxyxyxxyxxyxu{m^\UECB027"38"39!4;#5<#6<#5<#5;#4:"5:$6:%HG3[UCpeUuftyxxyxxyxxyxxyxxvntttssr~ryyyyyyyyyyyyyyyyyyyyyyvwi}n`uhYqfVqfVsgWzl]te{mxyyyyyyyyyyyyyyyyyyy}ruutssr}vxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyv~tttssrxswyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxwxstttssrvoxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvouutssrunxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxuotttssrvoxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxxvotttssr~wqvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvwruutssr}zsxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxys~ztttssrwnxxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxxntttssryusxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxsyuuutssrvpuxyxxyxxyxxyxxyxxyxxyxxxyxxyxtvptttssruo~sxxyxxyxxyxxyxxyxxyxxxyxx~rvptttssr~zwntxyyyyyyyyyyyyyyyyxtwo~{uutssrvpxo~rvxxxyxxyxv~rxovptttssr}}}}tttssruutssrtttrrqssrllknnmoooHttsGssriihjjixxwvvujjikkjyyxuutnnmimmlmmlpppgyyxssryyy ~{{z tts???????????????????????????????????????????????????????????????????????????????(@ BmmlllkOppo\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\qqp\ppo\mmlOnnmoon\귷qqp[mml?rrq>zzy||{~{{~w~sxyyyyyyyyx~sw~sxyyyyyyyyyyyyyyx~styyyyyyyyyyyyyyyyyyyys~uxyyyyyyyyyyyyyyyyyyyyyyx}ttyyyyyyyyyyyyyyyyyyyyyyyyyytvyyyyyyyyyyyyyyyyyyyyyyyyyyyyvvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvuyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyu~syyyyyyyyyyyyyx}ntcyn\wlYyn\tdpxyyyyyyyyyyyy~szyyyyyyyyyyyuwlZYV@BG->E)>E)=D(E)GK1]ZDwlZpyyyyyyyyyyzwyyyyyyyyywoeSCG.?G*BJ+AI+F)}p_yyyyyyyyyyyrd5;#@E+xyyyyyyyyyytxyyyyyyyq;@(BJ+DL-DL-BJ,PP9xyyyyyyyyyyyyJH64:#~pyyyyyyyyyyy~yyyyyyyyjaP=D(CK,DL-DL-@H*vkYyyyyyyyyyyyyywiviZxyyyyyyyyyyy{qyyyyyyyyJJ5@H*DL-DL-CK,?E*ryyyyyyyyyyyr|p^qhUraxyyyyyyyyyyy{qsyyyyyyyv8=&BJ,DL-DL-CK,KM5yyyyyyyyyyyya[HYX@rbZYAfcKyyyyyyyyyyy~svyyyyyyyzk7=$CK,DL-DL-CK,YW@yyyyyyyyyyyyug~oyudMU4tyyyyyyyyyyuxyyyyyyysd8?%CK,DL-DL-BJ,a]GyyyyyyyyyyyyyyysaNU4tyyyyyyyyyywyyyyyyyyrc8>%CK,DL-DL-BJ,e_Jyyyyyyyyyyyy}nb_GhcLNS6gbLyyyyyyyyyyyxwyyyyyyywi6=$CJ,DL-DL-BJ,c^IyyyyyyyyyyyytdXX?ravgxyyyyyyyyyyywvyyyyyyyr5;#BJ+DL-DL-CK,]ZDyyyyyyyyyyyyuf[[Ayyyyyyyyyyyyyyvsyyyyyyyy?A,@H*DL-DL-CK,PQ9yyyyyyyyyyyyvgKQ3_`D^^CfbKyyyyyyyyyyy~s{qyyyyyyyyZTCE)zkyyyyyyyyyyyxxyyyyyyyyxaYI5<#@G*CK,BJ,SS;ryyyyyyy~oFH1AI+?F*~oyyyyyyyyyyx}tyyyyyyyyyyocT6:$F)?G*CH-TTF)AH+@H*>F)7>%yyyyyyyyyywzyyyyyyyyyyyy{mh^NJI67;%6<$7>$8?%8?%7>$7=%:>(LJ6_YGsgWyyyyyyyyyyz~syyyyyyyyyyyyyyw{msdqaqbvh~pxyyyyyyyyyyyy~suyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvvyyyyyyyyyyyyyyyyyyyyyyyyyyyyvtyyyyyyyyyyyyyyyyyyyyyyyyyyt}txyyyyyyyyyyyyyyyyyyyyyyx~ttyyyyyyyyyyyyyyyyyyyytsxyyyyyyyyyyyyyyxs~vsxyyyyyyyyxs~v}{{}{{z||{oon@ssr?ppo]귷ssr\ppoqqpNvvv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\wwv\rrqNqqq??(0` %YYXddc4ggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEggfEddd4\\\jjiùnnm~vtuutv~vxxxxxxxxxxvwxyyxyyxyyxyyxywvyyxyyxyyxyyxyyxyyyvwxyyxyyxyyxyyxyyxyyyxwwxxxxxxxxxxxxxxxxxxxxxxwvxyxyyxyyxyxvvwxyyxyyyxyyvwxxxxxxxx|mmeQVU=GK1>E*;B'@E+LN5_[EvlYpxxxxxxxwxyxyxyyp\XC@F*BJ+?F)IJ3e]KmcRb[IEF0F)CK,CK,WW?xxxxxxxxxvjYidMgbK}p_xxxxxxxxxyyyxy{m8>%CK,CK,DJ-sxyyxyyxyyqbuyyxyyxyyxtyyyxyxYTB>F)CK,CK,phTxyyxyyxy}n9?&udyyxyyxyytxxxxxxrAC-@H*CK,FK/|mxxxxxxxa[H>E)|mxxxxxxxxwyyxyxyzl@B->F)CK,KO4zjyxyyxujY?F*@G+ryyxyyxywyyxyxyyqVQ?8?%?G*BI,a]G{o]scvlYWV?@G*BJ,>E)wyyxyyxyvyxyxyyxywh_YGDE08>%8?%9@&9@&9@&>B*ON9aZHyyyxyyxvxxxxxxxxxxxtyjvgxiqxxxxxxxxxxxyxyyxyyxyyxyyxyyxyyyxyxxxyyxyyxyyxyyxyyxyyyxxwxxxxxxxxxxxxxxxxxxwvxxyyxyyxyyxyyxyxvuxyxyyxyyxyyxutvxxxxvtnnm1qqq0mmlvvupwwvpoon( @ BBBEED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.EED.CCBzzyd||{c~wyyyyw~{yyyyyyyyyy{wyyyyyyyyyyyywxyyyyyyyyyyyyyyxwyyyyxtcjdO^[DZXAd_IwlY~oyyyywyyyyvfMO5AI+NO7shW{n^g_M@E+AH+pyyyyxyyyueBI,CK,YW@wyyywh?D*zn\yyyyxyyywHK2CK,EK/ryyyyyg_MhaMyyyyyxyyy{n]AI+CK,b^Hyyyyywvezkyyyyyxvyyyd^KCK,CK,wlYyyyyywhtbdaIwyyyyvxyyy]XDCK,CK,rayyyyyvtaa_Fwyyyyxxyyyb[HCK,CK,~q_yyyyy~oskVtyyyyywuyyythXAH+CK,qiUyyyyyrc`HfbKyyyyyuyyys?D+CK,WW>yyyyyyxl[ryyyyyyyyypeU>F)CJ,sbyyyytCG.pyyyyywyyyxh`N=D(FL/|p^xyqWV>AH+uyyyywyyyyyvgYUA@D+AE,GJ2. """ import wx import Resources.CeciliaLib as CeciliaLib import Resources.Control as Control import Resources.Preset as Preset from .constants import * from .Sliders import buildHorizontalSlidersBox from .Grapher import getGrapher, buildGrapher from .TogglePopup import buildTogglePopupBox from .menubar import InterfaceMenuBar class CeciliaInterface(wx.Frame): if CeciliaLib.getVar("systemPlatform").startswith('linux'): style = wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | \ wx.SYSTEM_MENU | wx.CAPTION | wx.CLIP_CHILDREN | wx.WANTS_CHARS else: style = wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | \ wx.SYSTEM_MENU | wx.CAPTION | wx.BORDER_SUNKEN | \ wx.CLIP_CHILDREN | wx.WANTS_CHARS def __init__(self, parent, id=-1, title='', mainFrame=None, pos=wx.DefaultPosition, size=wx.DefaultSize, style=style): wx.Frame.__init__(self, parent, id, title, pos, size, style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.ceciliaMainFrame = mainFrame self.menubar = InterfaceMenuBar(self, self.ceciliaMainFrame) self.SetMenuBar(self.menubar) self.box = wx.GridBagSizer(0, 0) self.controlBox = wx.BoxSizer(wx.VERTICAL) self.controlPanel = Control.CECControl(self, -1) togglePopupPanel, objs, tpsize = self.createTogglePopupPanel() slidersPanel, slPanelSize = self.createSlidersPanel() self.grapher = getGrapher(self) CeciliaLib.setVar("grapher", self.grapher) self.presetPanel = Preset.CECPreset(self) CeciliaLib.setVar("presetPanel", self.presetPanel) self.controlBox.Add(self.controlPanel, 1, wx.EXPAND | wx.RIGHT, -1) self.controlBox.Add(self.presetPanel, 0, wx.EXPAND | wx.TOP | wx.RIGHT, -1) self.controlBox.Add(togglePopupPanel, 0, wx.EXPAND | wx.TOP | wx.RIGHT, -1) self.box.Add(self.controlBox, (0, 0), span=(2, 1), flag=wx.EXPAND) self.box.Add(self.grapher, (0, 1), flag=wx.EXPAND) self.box.Add(slidersPanel, (1, 1), flag=wx.EXPAND | wx.TOP, border=-1) self.box.AddGrowableCol(1, 1) self.box.AddGrowableRow(0, 1) pos = CeciliaLib.getVar("interfacePosition") size = CeciliaLib.getVar("interfaceSize") pos, size = self.positionToClientArea(pos, size) self.SetSizer(self.box) self.SetSize(size) self.Bind(wx.EVT_CLOSE, self.onClose) if pos is None: self.Center() else: self.SetPosition(pos) self.Show() wx.CallLater(100, self.createGrapher) def positionToClientArea(self, pos, size): position = None screen = 0 if pos is not None: for i in range(CeciliaLib.getVar("numDisplays")): off = CeciliaLib.getVar("displayOffset")[i] dispsize = CeciliaLib.getVar("displaySize")[i] Xbounds = [off[0], dispsize[0] + off[0]] Ybounds = [off[1], dispsize[1] + off[1]] if pos[0] >= Xbounds[0] and pos[0] <= Xbounds[1] and \ pos[1] >= Ybounds[0] and pos[1] <= Ybounds[1]: position = pos screen = i break dispsize = CeciliaLib.getVar("displaySize")[screen] if size[0] <= dispsize[0] and size[1] <= dispsize[1]: newsize = size else: newsize = (dispsize[0] - 50, dispsize[1] - 50) return position, newsize def updateTitle(self, title): self.SetTitle(title) def createGrapher(self, evt=None): buildGrapher(self.grapher) for slider in CeciliaLib.getVar("userSliders"): slider.refresh() if CeciliaLib.getVar("presetToLoad") is not None: CeciliaLib.loadPresetFromFile(CeciliaLib.getVar("presetToLoad")) CeciliaLib.setVar("presetToLoad", None) def createTogglePopupPanel(self): panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) panel.SetBackgroundColour(BACKGROUND_COLOUR) widgets = CeciliaLib.getVar("interfaceWidgets") box, objs = buildTogglePopupBox(panel, widgets) panel.SetSizerAndFit(box) CeciliaLib.setVar("userTogglePopups", objs) size = panel.GetSize() return panel, objs, size def createSlidersPanel(self): panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) panel.SetBackgroundColour(BACKGROUND_COLOUR) widgets = CeciliaLib.getVar("interfaceWidgets") box, sl = buildHorizontalSlidersBox(panel, widgets) CeciliaLib.setVar("userSliders", sl) panel.SetSizerAndFit(box) size = panel.GetSize() return panel, size def onClose(self, event): CeciliaLib.setVar("interfaceSize", self.GetSize()) CeciliaLib.setVar("interfacePosition", self.GetPosition()) CeciliaLib.getVar("mainFrame").SetFocus() CeciliaLib.getVar("mainFrame").Hide() CeciliaLib.resetWidgetVariables() # Cleanup menubar self.menubar.frame = None self.Unbind(wx.EVT_MENU, handler=self.onUndo, id=ID_UNDO) self.Unbind(wx.EVT_MENU, handler=self.onRedo, id=ID_REDO) self.Unbind(wx.EVT_MENU, handler=self.onCopy, id=ID_COPY) self.Unbind(wx.EVT_MENU, handler=self.onPaste, id=ID_PASTE) self.Unbind(wx.EVT_MENU, handler=self.onSelectAll, id=ID_SELECT_ALL) self.Unbind(wx.EVT_CLOSE, handler=self.onClose) self.controlPanel.parent = None # Cleanup Grapher.plotter self.grapher.plotter.canvas.Unbind(wx.EVT_LEFT_DOWN, handler=self.grapher.plotter.OnMouseLeftDown) self.grapher.plotter.canvas.Unbind(wx.EVT_LEFT_UP, handler=self.grapher.plotter.OnMouseLeftUp) self.grapher.plotter.canvas.Unbind(wx.EVT_MOTION, handler=self.grapher.plotter.OnMotion) self.grapher.plotter.canvas.Unbind(wx.EVT_LEFT_DCLICK, handler=self.grapher.plotter.OnMouseDoubleClick) self.grapher.plotter.canvas.Unbind(wx.EVT_RIGHT_DOWN, handler=self.grapher.plotter.OnMouseRightDown) self.grapher.plotter.canvas.Unbind(wx.EVT_LEAVE_WINDOW, handler=self.grapher.plotter.OnLeave) self.grapher.plotter.canvas.Unbind(wx.EVT_PAINT, handler=self.grapher.plotter.OnPaint) self.grapher.plotter.canvas.Unbind(wx.EVT_SIZE, handler=self.grapher.plotter.OnSize) self.grapher.plotter.canvas.Unbind(wx.EVT_LEAVE_WINDOW, handler=self.grapher.plotter.OnLooseFocus) self.grapher.plotter.canvas.Unbind(wx.EVT_ENTER_WINDOW, handler=self.grapher.plotter.OnGrabFocus) self.grapher.plotter.canvas.Unbind(wx.EVT_CHAR, handler=self.grapher.plotter.OnKeyDown) self.grapher.plotter.Unbind(wx.EVT_CHAR, handler=self.grapher.plotter.OnKeyDown) self.grapher.plotter.Unbind(wx.EVT_SCROLL_THUMBTRACK, handler=self.grapher.plotter.OnScroll) self.grapher.plotter.Unbind(wx.EVT_SCROLL_PAGEUP, handler=self.grapher.plotter.OnScroll) self.grapher.plotter.Unbind(wx.EVT_SCROLL_PAGEDOWN, handler=self.grapher.plotter.OnScroll) self.grapher.plotter.Unbind(wx.EVT_SCROLL_LINEUP, handler=self.grapher.plotter.OnScroll) self.grapher.plotter.Unbind(wx.EVT_SCROLL_LINEDOWN, handler=self.grapher.plotter.OnScroll) if '__WXGTK__' in wx.PlatformInfo: self.grapher.plotter.canvas.Unbind(wx.EVT_WINDOW_CREATE, handler=self.grapher.plotter.doOnSize) self.grapher.plotter.parent = None self.grapher.plotter = None # Cleanup Grapher.toolbar self.grapher.toolbar.Unbind(wx.EVT_LEAVE_WINDOW, handler=self.grapher.toolbar.OnLooseFocus) self.grapher.toolbar.fakePanel.Unbind(wx.EVT_LEAVE_WINDOW, handler=self.grapher.toolbar.OnLooseFocus) self.grapher.toolbar.Unbind(wx.EVT_CHAR, handler=self.grapher.toolbar.OnKeyDown) self.grapher.toolbar.convertSlider.cecGrapher = None self.grapher.toolbar.radiotoolbox.outFunction = None self.grapher.toolbar.menu.outFunction = None self.grapher.toolbar.toolbox.outFunction = None self.grapher.toolbar.toolbox.parent = None self.grapher.toolbar.parent = None self.grapher.toolbar = None # Cleanup Grapher.cursorPanel self.grapher.cursorPanel.Unbind(wx.EVT_LEFT_DOWN, handler=self.grapher.cursorPanel.OnLeftDown) self.grapher.cursorPanel.Unbind(wx.EVT_LEAVE_WINDOW, handler=self.grapher.cursorPanel.OnLooseFocus) self.grapher.cursorPanel.Unbind(wx.EVT_PAINT, handler=self.grapher.cursorPanel.OnPaint) self.grapher.cursorPanel.parent = None self.grapher.cursorPanel = None # Cleanup PresetPanel self.presetPanel.presetChoice = None self.presetPanel.saveTool = None try: self.Destroy() CeciliaLib.setVar("interface", None) except: pass def getControlPanel(self): return self.controlPanel def onUndo(self, evt): self.grapher.plotter.undoRedo(1) def onRedo(self, event): self.grapher.plotter.undoRedo(-1) def onCopy(self, event): self.grapher.plotter.onCopy() def onPaste(self, event): self.grapher.plotter.onPaste() def onSelectAll(self, event): self.grapher.plotter.onSelectAll() def updateNchnls(self): self.controlPanel.updateNchnls() cecilia5-5.4.1/Resources/CeciliaLib.py000066400000000000000000001063071372272363700175260ustar00rootroot00000000000000# encoding: utf-8 """ Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import os, sys, wx, time, math, copy, codecs, shutil import unicodedata from subprocess import Popen from .constants import * from .API_interface import * import Resources.Variables as vars import wx.lib.agw.supertooltip as STT import xmlrpc.client as xmlrpclib if sys.version_info[0] < 3: unicode_t = unicode else: unicode_t = str def buildFileTree(): root = MODULES_PATH directories = [] files = {} for dir in sorted(os.listdir(MODULES_PATH)): if not dir.startswith('.'): directories.append(dir) files[dir] = [] for f in sorted(os.listdir(os.path.join(root, dir))): if not f.startswith('.'): files[dir].append(f) return root, directories, files def setVar(var, value): vars.CeciliaVar[var] = value def getVar(var, unicode=False): if unicode: return ensureNFD(vars.CeciliaVar[var]) else: return vars.CeciliaVar[var] def setJackParams(client=None, inPortName=None, outPortName=None): if client is not None: vars.CeciliaVar['jack']['client'] = client if inPortName is not None: vars.CeciliaVar['jack']['inPortName'] = inPortName if outPortName is not None: vars.CeciliaVar['jack']['outPortName'] = outPortName def setPlugins(x, pos): vars.CeciliaVar['plugins'][pos] = x def getControlPanel(): return getVar('interface').getControlPanel() def writeVarToDisk(): vars.writeCeciliaPrefsToFile() def chooseColour(i, numlines): def clip(x): val = int(x * 255) if val < 0: val = 0 elif val > 255: val = 255 else: val = val return val def colour(i, numlines, sat, bright): hue = (i / numlines) * 315 segment = math.floor(hue / 60) % 6 fraction = hue / 60 - segment t1 = bright * (1 - sat) t2 = bright * (1 - (sat * fraction)) t3 = bright * (1 - (sat * (1 - fraction))) if segment == 0: r, g, b = bright, t3, t1 elif segment == 1: r, g, b = t2, bright, t1 elif segment == 2: r, g, b = t1, bright, t3 elif segment == 3: r, g, b = t1, t2, bright elif segment == 4: r, g, b = t3, t1, bright elif segment == 5: r, g, b = bright, t1, t2 return wx.Colour(clip(r), clip(g), clip(b)) lineColour = colour(i, numlines, 1, 1) midColour = colour(i, numlines, .5, .5) knobColour = colour(i, numlines, .8, .5) sliderColour = colour(i, numlines, .5, .75) return [lineColour, midColour, knobColour, sliderColour] def chooseColourFromName(name): def clip(x): val = int(x * 255) if val < 0: val = 0 elif val > 255: val = 255 else: val = val return val def colour(name): vals = COLOUR_CLASSES[name] hue = vals[0] bright = vals[1] sat = vals[2] segment = int(math.floor(hue / 60)) fraction = hue / 60 - segment t1 = bright * (1 - sat) t2 = bright * (1 - (sat * fraction)) t3 = bright * (1 - (sat * (1 - fraction))) if segment == 0: r, g, b = bright, t3, t1 elif segment == 1: r, g, b = t2, bright, t1 elif segment == 2: r, g, b = t1, bright, t3 elif segment == 3: r, g, b = t1, t2, bright elif segment == 4: r, g, b = t3, t1, bright elif segment == 5: r, g, b = bright, t1, t2 return wx.Colour(clip(r), clip(g), clip(b)) lineColour = colour(name) midColour = colour(name) knobColour = colour(name) sliderColour = colour(name) return [lineColour, midColour, knobColour, sliderColour] ### Tooltips ### class CECTooltip(STT.SuperToolTip): def __init__(self, tip): STT.SuperToolTip.__init__(self, tip) self.SetStartDelay(1.5) if getVar("useTooltips"): self.EnableTip(True) else: self.EnableTip(False) self.ApplyStyle("Gray") def OnMouseEvents(self, evt): if evt.ButtonDown() or evt.ButtonDClick(): self.DoHideNow() evt.Skip() def update(self): if getVar("useTooltips"): self.EnableTip(True) else: self.EnableTip(False) def setToolTip(obj, tip): if "\n" in tip: pos = tip.find("\n") header = tip[:pos] body = tip[pos+1:] else: header = "Documentation" body = tip tooltip = CECTooltip(body) tooltip.SetHeader(header) tooltip.SetTarget(obj) tooltip.SetDrawHeaderLine(True) getVar("tooltips").append(tooltip) obj.Bind(wx.EVT_MOUSE_EVENTS, tooltip.OnMouseEvents) def updateTooltips(): for tooltip in getVar("tooltips"): tooltip.update() ###### Start / Stop / Drivers ###### def startCeciliaSound(timer=True, rec=False): # Check if soundfile is loaded for key in getVar("userInputs").keys(): if 'mode' not in getVar("userInputs")[key]: getVar("userInputs")[key]['mode'] = 0 if getVar("userInputs")[key]['mode'] == 0: if not os.path.isfile(getVar("userInputs")[key]['path']): showErrorDialog('No input sound file!', 'In/Out panel, "%s" has no input sound file, please load one...' % getControlPanel().getCfileinFromName(key).label) ret = getControlPanel().getCfileinFromName(key).onLoadFile() if not ret: resetControls() getVar("grapher").toolbar.loadingMsg.SetForegroundColour(TITLE_BACK_COLOUR) wx.CallAfter(getVar("grapher").toolbar.loadingMsg.Refresh) return getControlPanel().resetMeter() getVar("audioServer").shutdown() getVar("audioServer").reinit() getVar("audioServer").boot() if getVar("currentModuleRef") is not None: if not getVar("audioServer").loadModule(getVar("currentModuleRef")): return else: showErrorDialog("Wow...!", "No module to load.") return getVar("grapher").toolbar.convertSlider.Hide() getVar("presetPanel").presetChoice.setEnable(False) getControlPanel().durationSlider.Disable() getVar("audioServer").start(timer=timer, rec=rec) if getVar('showSpectrum'): getVar('mainFrame').openSpectrumWindow() getVar("grapher").toolbar.loadingMsg.SetForegroundColour(TITLE_BACK_COLOUR) wx.CallAfter(getVar("grapher").toolbar.loadingMsg.Refresh) def stopCeciliaSound(): if getVar('spectrumFrame') is not None: try: getVar('spectrumFrame').onClose() except: getVar('interface').menubar.spectrumSwitch(False) setVar('showSpectrum', 0) finally: setVar('spectrumFrame', None) if getVar("audioServer").isAudioServerRunning(): getVar("audioServer").stop() time.sleep(.25) if getVar("currentModule") is not None: getVar("audioServer").checkForAutomation() getVar("currentModule")._checkForAutomation() getVar("grapher").checkForAutomation() resetControls() def resetControls(): if getVar('interface') is not None: getControlPanel().transportButtons.setPlay(False) getControlPanel().transportButtons.setRecord(False) getVar("presetPanel").presetChoice.setEnable(True) getControlPanel().durationSlider.Enable() if getControlPanel().tmpTotalTime != getVar("totalTime"): getControlPanel().setTotalTime(getControlPanel().tmpTotalTime, True) wx.CallAfter(getControlPanel().vuMeter.reset) def queryAudioMidiDrivers(): inputs, inputIndexes, defaultInput, outputs, outputIndexes, \ defaultOutput, midiInputs, midiInputIndexes, defaultMidiInput = getVar("audioServer").getAvailableAudioMidiDrivers() setVar("availableAudioOutputs", outputs) setVar("availableAudioOutputIndexes", outputIndexes) if getVar("audioOutput") not in outputIndexes: try: setVar("audioOutput", outputIndexes[outputs.index(defaultOutput)]) except: setVar("audioOutput", 0) setVar("availableAudioInputs", inputs) setVar("availableAudioInputIndexes", inputIndexes) if getVar("audioInput") not in inputIndexes: try: setVar("audioInput", inputIndexes[inputs.index(defaultInput)]) except: setVar("audioInput", 0) if midiInputs == []: setVar("useMidi", 0) else: setVar("useMidi", 1) setVar("availableMidiInputs", midiInputs) setVar("availableMidiInputIndexes", midiInputIndexes) if getVar("midiDeviceIn") not in midiInputIndexes: try: setVar("midiDeviceIn", midiInputIndexes[midiInputs.index(defaultMidiInput)]) except: setVar("midiDeviceIn", 0) def openAudioFileDialog(parent, wildcard, type='open', defaultPath=os.path.expanduser('~')): setVar("canGrabFocus", False) openDialog = wx.FileDialog(parent, message='Choose a file to %s' % type, defaultDir=defaultPath, wildcard=wildcard, style=wx.FD_OPEN | wx.FD_PREVIEW) if openDialog.ShowModal() == wx.ID_OK: filePath = ensureNFD(openDialog.GetPath()) setVar("openAudioFilePath", os.path.split(filePath)[0]) else: filePath = None openDialog.Destroy() setVar("canGrabFocus", True) return filePath def saveFileDialog(parent, wildcard, type='Save'): if type == 'Save audio': defaultPath = getVar("saveAudioFilePath", unicode=True) ext = "." + getVar("audioFileType") else: defaultPath = getVar("saveFilePath", unicode=True) ext = ".c5" setVar("canGrabFocus", False) defaultFile = os.path.split(getVar("currentCeciliaFile", unicode=True))[1].split(".")[0] saveAsDialog = wx.FileDialog(parent, message="%s file as ..." % type, defaultDir=defaultPath, defaultFile=defaultFile + ext, wildcard=wildcard, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if saveAsDialog.ShowModal() == wx.ID_OK: filePath = ensureNFD(saveAsDialog.GetPath()) if type == 'Save audio': setVar("saveAudioFilePath", os.path.split(filePath)[0]) else: setVar("saveFilePath", os.path.split(filePath)[0]) else: filePath = None saveAsDialog.Destroy() setVar("canGrabFocus", True) return filePath def showErrorDialog(title, msg): setVar("canGrabFocus", False) if getVar("mainFrame") is not None: dlg = wx.MessageDialog(getVar("mainFrame"), msg, title, wx.OK) else: dlg = wx.MessageDialog(None, msg, title, wx.OK) dlg.ShowModal() dlg.Destroy() setVar("canGrabFocus", True) ###### External app calls ###### def loadPlayerEditor(app_type): if getVar("systemPlatform") == 'win32': wildcard = "Executable files (*.exe)|*.exe|" \ "All files|*" elif getVar("systemPlatform") == 'darwin': wildcard = "Application files (*.app)|*.app|" \ "All files|*" else: wildcard = "All files|*" setVar("canGrabFocus", False) path = '' dlg = wx.FileDialog(None, message="Choose a %s..." % app_type, defaultDir=ensureNFD(os.path.expanduser('~')), wildcard=wildcard, style=wx.FD_OPEN) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() dlg.Destroy() setVar("canGrabFocus", True) if path: if app_type == 'soundfile player': setVar("soundfilePlayer", path) elif app_type == 'soundfile editor': setVar("soundfileEditor", path) elif app_type == 'text editor': setVar("textEditor", path) def listenSoundfile(soundfile): if getVar("soundfilePlayer") == '': showErrorDialog("Preferences not set", "Choose a soundfile player first.") loadPlayerEditor('soundfile player') if os.path.isfile(soundfile): app = getVar("soundfilePlayer") if getVar("systemPlatform") == 'darwin': cmd = 'open -a "%s" "%s"' % (app, soundfile) Popen(cmd, shell=True) elif getVar("systemPlatform") == 'win32': try: Popen([app, soundfile], shell=False) except (OSError, OSError2): print('Unable to open desired software:\n' + app) else: cmd = '"%s" "%s"' % (app, soundfile) try: Popen(cmd, shell=True) except (OSError, OSError2): print('Unable to open desired software:\n' + app) def editSoundfile(soundfile): if getVar("soundfileEditor") == '': showErrorDialog("Preferences not set", "Choose a soundfile editor first.") loadPlayerEditor('soundfile editor') if os.path.isfile(soundfile): app = getVar("soundfileEditor") if getVar("systemPlatform") == 'darwin': cmd = 'open -a "%s" "%s"' % (app, soundfile) Popen(cmd, shell=True) elif getVar("systemPlatform") == 'win32': try: Popen([app, soundfile], shell=False) except (OSError, OSError2): print('Unable to open desired software:\n' + app) else: cmd = '%s %s' % (app, soundfile) try: Popen(cmd, shell=True) except (OSError, OSError2): print('Unable to open desired software:\n' + app) def openCurrentFileAsText(curfile): if getVar("textEditor") == '': showErrorDialog("Preferences not set", "Choose a text editor first.") loadPlayerEditor('text editor') if os.path.isfile(curfile): app = getVar("textEditor") if getVar("systemPlatform") == 'darwin': cmd = 'open -a "%s" "%s"' % (app, os.path.join(os.getcwd(), curfile)) Popen(cmd, shell=True, cwd=os.path.expanduser("~")) elif getVar("systemPlatform") == 'win32': try: Popen([app, curfile], shell=False) except (OSError, OSError2): print('Unable to open desired software:\n' + app) else: cmd = '%s %s' % (app, curfile) try: Popen(cmd, shell=True) except (OSError, OSError2): print('Unable to open desired software:\n' + app) ###### Preset functions ###### def loadPresetFromFile(preset): presetFile = os.path.join(PRESETS_PATH, getVar("currentModuleName"), preset) presetData = None if preset == "init": presetData = getVar("initPreset") elif os.path.isfile(presetFile): with open(presetFile, 'r') as f: try: result, method = xmlrpclib.loads(f.read()) presetData = result[0] except: showErrorDialog("Preset corrupted...", "Failed to load preset '%s', reloading 'init'..." % preset) preset = "init" presetData = getVar("initPreset") if presetData is not None: currentModule = getVar("currentModule") setVar("currentModule", None) for data in presetData.keys(): if data == 'userInputs': if presetData[data] == {}: continue ok = True prekeys = presetData[data].keys() for key in prekeys: if not os.path.isfile(presetData[data][key]['path']): ok = False break if not getVar("rememberedSound"): if ok: setVar("userInputs", copy.deepcopy(presetData[data])) updateInputsFromDict() else: for input in getVar("userInputs"): cfilein = getControlPanel().getCfileinFromName(input) cfilein.reset() cfilein.reinitSamplerFrame() else: if ok: setVar("userInputs", copy.deepcopy(presetData[data])) updateInputsFromDict() else: for input in getVar("userInputs"): cfilein = getControlPanel().getCfileinFromName(input) cfilein.reinitSamplerFrame() elif data == 'userSliders': slidersDict = presetData[data] for slider in getVar("userSliders"): if slider.getName() in slidersDict: slider.setState(slidersDict[slider.getName()]) del slidersDict elif data == 'plugins': pluginsDict = deepCopy(presetData[data]) wx.CallAfter(getControlPanel().setPlugins, pluginsDict) del pluginsDict elif data == 'userTogglePopups': togDict = presetData[data] for widget in getVar("userTogglePopups"): if widget.getName() in togDict and hasattr(widget, "setValue"): widget.setValue(togDict[widget.getName()], True) del togDict else: continue if preset == "init": for line in getVar("grapher").getPlotter().getData(): try: line.reinit() except: pass elif 'userGraph' in presetData: graphDict = deepCopy(presetData['userGraph']) ends = ['min', 'max'] for line in graphDict: for i, graphLine in enumerate(getVar("grapher").getPlotter().getData()): if line == graphLine.getName(): graphLine.setLineState(graphDict[line]) break else: for end in ends: if graphLine.getLabel().endswith(end) and line.endswith(end) and line.startswith(graphLine.getName()): graphLine.setLineState(graphDict[line]) break del graphDict setVar("totalTime", presetData["totalTime"]) getControlPanel().updateDurationSlider() setVar("nchnls", presetData["nchnls"]) updateNchnlsDevices() getVar("gainSlider").SetValue(presetData["gainSlider"]) getVar("presetPanel").setLabel(preset) getVar("grapher").getPlotter().draw() setVar("currentModule", currentModule) getVar("grapher").setTotalTime(getVar("totalTime")) wx.CallAfter(againForPluginKnobs, presetData) ### This is a hack to ensure that plugin knob automations are drawn in the grapher. ### Called within a wx.CallAfter to be executed after wx.CallAfter(getControlPanel().setPlugins). def againForPluginKnobs(presetData): if 'userGraph' in presetData: graphDict = presetData['userGraph'] for line in graphDict: for i, graphLine in enumerate(getVar("grapher").getPlotter().getData()): if line == graphLine.getName(): graphLine.setLineState(graphDict[line]) break del graphDict getVar("grapher").getPlotter().draw() getVar("grapher").setTotalTime(getVar("totalTime")) def savePresetToFile(presetName): presetDict = dict() presetDict['nchnls'] = getVar("nchnls") presetDict['totalTime'] = getVar("totalTime") presetDict['gainSlider'] = getVar("gainSlider").GetValue() if getVar("interface"): presetDict['userInputs'] = completeUserInputsDict() sliderDict = dict() for slider in getVar("userSliders"): sliderDict[slider.getName()] = slider.getState() presetDict['userSliders'] = copy.deepcopy(sliderDict) del sliderDict widgetDict = dict() plugins = getVar("plugins") for i, plugin in enumerate(plugins): if plugin is None: widgetDict[str(i)] = ['None', [0, 0, 0, 0], [[0, 0, None], [0, 0, None], [0, 0, None]]] else: widgetDict[str(i)] = [plugin.getName(), plugin.getParams(), plugin.getStates()] presetDict['plugins'] = copy.deepcopy(widgetDict) del widgetDict widgetDict = dict() for widget in getVar("userTogglePopups"): widgetDict[widget.getName()] = widget.getValue() presetDict['userTogglePopups'] = copy.deepcopy(widgetDict) del widgetDict graphDict = dict() for line in getVar("grapher").getPlotter().getData(): if line.slider is None: graphDict[line.getName()] = line.getLineState() else: outvalue = line.slider.getValue() if line.slider.widget_type in ["slider", "plugin_knob"]: graphDict[line.getName()] = line.getLineState() elif line.slider.widget_type == "range": ends = ['min', 'max'] for i in range(len(outvalue)): if line.getLabel().endswith(ends[i]): graphDict[line.getName() + ends[i]] = line.getLineState() break elif line.slider.widget_type == "splitter": for i in range(len(outvalue)): if line.getLabel().endswith("%d" % i): graphDict[line.getName() + "_%d" % i] = line.getLineState() break presetDict['userGraph'] = copy.deepcopy(graphDict) del graphDict if presetName == "init": setVar("initPreset", deepCopy(presetDict)) else: with open(os.path.join(PRESETS_PATH, getVar('currentModuleName'), presetName), "w") as presetFile: msg = xmlrpclib.dumps((presetDict, ), allow_none=True) presetFile.write(msg) def completeUserInputsDict(): for i in getVar("userInputs"): try: getVar("userInputs")[i]['mode'] = 0 if getVar("userInputs")[i]['type'] == 'csampler': cfilein = getControlPanel().getCfileinFromName(i) getVar("userInputs")[i]['off' + cfilein.getName()] = cfilein.getOffset() getVar("userInputs")[i]['loopMode'] = cfilein.getSamplerInfo()['loopMode'] getVar("userInputs")[i]['startFromLoop'] = cfilein.getSamplerInfo()['startFromLoop'] getVar("userInputs")[i]['loopX'] = cfilein.getSamplerInfo()['loopX'] getVar("userInputs")[i]['loopIn'] = cfilein.getSamplerInfo()['loopIn'] getVar("userInputs")[i]['loopOut'] = cfilein.getSamplerInfo()['loopOut'] getVar("userInputs")[i]['gain'] = cfilein.getSamplerInfo()['gain'] getVar("userInputs")[i]['transp'] = cfilein.getSamplerInfo()['transp'] elif getVar("userInputs")[i]['type'] == 'cfilein': cfilein = getControlPanel().getCfileinFromName(i) getVar("userInputs")[i]['off' + cfilein.getName()] = cfilein.getOffset() except: pass return copy.deepcopy(getVar("userInputs")) def updateInputsFromDict(): for input in getVar("userInputs"): cfilein = getControlPanel().getCfileinFromName(input) if cfilein and os.path.isfile(getVar("userInputs")[input]['path']): inputDict = getVar("userInputs")[input] cfilein.updateMenuFromPath(inputDict['path']) for k in inputDict: if k == 'loopMode': cfilein.getSamplerFrame().setLoopMode(inputDict[k]) elif k == 'loopX': cfilein.getSamplerFrame().setLoopX(inputDict[k]) elif k == 'loopIn': cfilein.getSamplerFrame().setLoopIn(inputDict[k]) elif k == 'loopOut': cfilein.getSamplerFrame().setLoopOut(inputDict[k]) elif k == 'gain': cfilein.getSamplerFrame().setGain(inputDict[k]) elif k == 'transp': cfilein.getSamplerFrame().setTransp(inputDict[k]) elif k == 'startFromLoop': cfilein.getSamplerFrame().setStartFromLoop(inputDict[k]) elif k == ('off' + input): cfilein.setOffset(inputDict[k]) elif k == 'path': pass ###### Open / Save / Close ###### def saveCompileBackupFile(cecFilePath): with open(cecFilePath, "r") as f: _module = f.read() with open(MODULE_COMPILE_BACKUP_PATH, "w") as f: f.write(_module) def saveRuntimeBackupFile(cecFilePath): with open(cecFilePath, "r") as f: _module = f.read() with open(MODULE_RUNTIME_BACKUP_PATH, "w") as f: f.write(_module) def saveCeciliaFile(parent): wildcard = "Cecilia file (*.%s)|*.%s" % (FILE_EXTENSION, FILE_EXTENSION) fileToSave = saveFileDialog(parent, wildcard, 'Save') if not fileToSave: return else: if not fileToSave.endswith(FILE_EXTENSION): fileToSave = "%s.%s" % (fileToSave, FILE_EXTENSION) savePresetToFile("last save") curfile = codecs.open(getVar("currentCeciliaFile", unicode=True), "r", encoding="utf-8") curtext = curfile.read() curfile.close() try: file = codecs.open(fileToSave, "w", encoding="utf-8") except IOError: setVar("canGrabFocus", False) dlg = wx.MessageDialog(parent, 'Please verify permissions and write access on the file and try again.', '"%s" could not be opened for writing' % (fileToSave), wx.OK | wx.ICON_EXCLAMATION) if dlg.ShowModal() == wx.ID_OK: dlg.Destroy() setVar("canGrabFocus", True) return file.write(curtext.rstrip()) file.close() oldModuleName = os.path.split(getVar("currentCeciliaFile"))[1] oldModuleName = os.path.splitext(oldModuleName)[0] newModuleName = os.path.split(fileToSave)[1] newModuleName = os.path.splitext(newModuleName)[0] if not os.path.isdir(os.path.join(PRESETS_PATH, newModuleName)): os.mkdir(os.path.join(PRESETS_PATH, newModuleName)) for f in os.listdir(os.path.join(PRESETS_PATH, oldModuleName)): shutil.copyfile(os.path.join(PRESETS_PATH, oldModuleName, f), os.path.join(PRESETS_PATH, newModuleName, f)) setVar("builtinModule", False) setVar('currentModuleName', newModuleName) setVar("currentCeciliaFile", fileToSave) setVar("lastCeciliaFile", fileToSave) if getVar("mainFrame") is not None: getVar("mainFrame").newRecent(fileToSave) saveCompileBackupFile(fileToSave) def openCeciliaFile(parent, openfile=None, builtin=False): if not openfile: setVar("canGrabFocus", False) wildcard = "Cecilia file (*.%s)|*.%s" % (FILE_EXTENSION, FILE_EXTENSION) defaultPath = getVar("openFilePath", unicode=True) openDialog = wx.FileDialog(parent, message='Choose a Cecilia file to open...', defaultDir=defaultPath, wildcard=wildcard, style=wx.FD_OPEN) if openDialog.ShowModal() == wx.ID_OK: cecFilePath = openDialog.GetPath() setVar("openFilePath", (os.path.split(cecFilePath)[0])) else: cecFilePath = None openDialog.Destroy() setVar("canGrabFocus", True) if cecFilePath is None: return else: cecFilePath = openfile if getVar("audioServer").isAudioServerRunning(): stopCeciliaSound() snds = [] if getVar("rememberedSound") and getVar("interfaceWidgets") and getVar("userInputs"): try: names = [d['name'] for d in getVar("interfaceWidgets")] keys = getVar("userInputs").keys() sortlist = list(zip([names.index(k) for k in keys], keys)) sortlist.sort() index, keys = list(zip(*sortlist)) for key in keys: if getVar("userInputs")[key]['path'] != '': snds.append(getVar("userInputs")[key]['path']) except: pass if getVar("currentCeciliaFile"): closeCeciliaFile(parent) moduleName = os.path.split(cecFilePath)[1] moduleName = os.path.splitext(moduleName)[0] setVar('currentModuleName', moduleName) if not os.path.isdir(os.path.join(PRESETS_PATH, moduleName)): os.mkdir(os.path.join(PRESETS_PATH, moduleName)) getVar("mainFrame").Hide() if not getVar("audioServer").openCecFile(cecFilePath): return setVar("builtinModule", builtin) setVar("currentCeciliaFile", cecFilePath) setVar("lastCeciliaFile", cecFilePath) if getVar("mainFrame") is not None: getVar("mainFrame").newRecent(cecFilePath) saveCompileBackupFile(cecFilePath) if getVar("interface"): for i, cfilein in enumerate(getControlPanel().getCfileinList()): if i >= len(snds): break cfilein.onLoadFile(snds[i]) savePresetToFile("init") if os.path.isfile(os.path.join(PRESETS_PATH, moduleName, "last save")): setVar("presetToLoad", "last save") getVar("mainFrame").updateTitle() getVar("interface").Raise() def closeCeciliaFile(parent): savePresetToFile("last save") getVar("mainFrame").closeInterface() setVar("currentCeciliaFile", '') ###### Interface creation utilities ###### def resetWidgetVariables(): setVar("gainSlider", None) setVar("plugins", [None] * NUM_OF_PLUGINS) setVar("userInputs", {}) for slider in getVar("userSliders"): slider.cleanup() setVar("userSliders", []) setVar("userSamplers", []) setVar("userTogglePopups", []) setVar("samplerSliders", []) setVar("samplerTogglePopup", []) getVar("presetPanel").cleanup() getVar("presetPanel").parent = None setVar("presetPanel", None) getVar("grapher").parent = None setVar("grapher", None) setVar("tooltips", []) def parseInterfaceText(): interfaceWidgets = getVar("interfaceWidgets") return interfaceWidgets def updateNchnlsDevices(): try: getVar("interface").updateNchnls() except: pass def deepCopy(ori): if type(ori) is dict: new = {} for key in ori: new[key] = deepCopy(ori[key]) return new elif type(ori) is list: new = [] for data in ori: new.append(deepCopy(data)) return new elif type(ori) is tuple: new = [] for data in ori: new.append(deepCopy(data)) return new else: return ori return new ###### Conversion functions ####### def interpFloat(t, v1, v2): "interpolator for a single value; interprets t in [0-1] between v1 and v2" return (v2 - v1) * t + v1 def tFromValue(value, v1, v2): "returns a t (in range 0-1) given a value in the range v1 to v2" return (value - v1) / (v2 - v1) def clamp(v, minv, maxv): "clamps a value within a range" if v < minv: v = minv if v > maxv: v = maxv return v def toLog(t, v1, v2): v1 = float(v1) return math.log10(t / v1) / math.log10(v2 / v1) def toExp(t, v1, v2): return math.pow(10, t * (math.log10(v2) - math.log10(v1)) + math.log10(v1)) ###### Utility functions ####### def autoRename(path, index=0, wrap=False): if os.path.exists(path): file = ensureNFD(os.path.split(path)[1]) if wrap: name = ensureNFD(file.rsplit('.', 1)[0])[:-4] else: name = ensureNFD(file.rsplit('.', 1)[0]) ext = file.rsplit('.', 1)[1] if len(name) >= 5: if name[-4] == '_' and name[-3:].isdigit(): name = name[:-4] root = os.path.split(path)[0] filelist = os.listdir(root) num = index for f in filelist: f = ensureNFD(f) if name in f and ext in f: num += 1 newName = name + '_%03d' % num + '.' + ext newPath = os.path.join(root, newName) return autoRename(newPath, index + 1, True) else: newPath = path return newPath def shortenName(name, maxChar): name = ensureNFD(name) if len(name) > maxChar: shortenChar = '...' addSpace = 0 charSpace = (maxChar - len(shortenChar)) // 2 if (maxChar - 5) % 2 != 0: addSpace = 1 name = name[:charSpace + addSpace] + shortenChar + name[len(name) - charSpace:] return name def ensureNFD(unistr): if getVar("systemPlatform").startswith('linux') or sys.platform == 'win32': encodings = [DEFAULT_ENCODING, ENCODING, 'cp1252', 'iso-8859-1', 'utf-16'] format = 'NFC' else: encodings = [DEFAULT_ENCODING, ENCODING, 'macroman', 'iso-8859-1', 'utf-16'] format = 'NFC' decstr = unistr if type(decstr) != unicode_t: for encoding in encodings: try: decstr = decstr.decode(encoding) break except UnicodeDecodeError: continue except: decstr = "UnableToDecodeString" print("Unicode encoding not in a recognized format...") break if decstr == "UnableToDecodeString": return unistr else: return unicodedata.normalize(format, decstr) def checkForPresetsInCeciliaFile(filepath): PRESETS_DELIMITER = "####################################\n" \ "##### Cecilia reserved section #####\n" \ "#### Presets saved from the app ####\n" \ "####################################\n" mylocals = {} rewrite = False with open(filepath, "r") as f: text = f.read() if PRESETS_DELIMITER in text: rewrite = True newtext = text[:text.find(PRESETS_DELIMITER) - 1] text = text[text.find(PRESETS_DELIMITER) + len(PRESETS_DELIMITER) + 1:] if text.strip() != "": exec(text, globals(), mylocals) presetsDir = os.path.join(PRESETS_PATH, getVar("currentModuleName")) if not os.path.isdir(presetsDir): os.mkdir(presetsDir) for preset in mylocals["CECILIA_PRESETS"]: for key in list(mylocals["CECILIA_PRESETS"][preset]["plugins"]): mylocals["CECILIA_PRESETS"][preset]["plugins"][str(key)] = mylocals["CECILIA_PRESETS"][preset]["plugins"][key] del mylocals["CECILIA_PRESETS"][preset]["plugins"][key] msg = xmlrpclib.dumps((mylocals["CECILIA_PRESETS"][preset], ), allow_none=True) with open(os.path.join(presetsDir, preset), "w") as fw: fw.write(msg) if rewrite: with open(filepath, "w") as f: f.write(newtext) cecilia5-5.4.1/Resources/CeciliaMainFrame.py000077500000000000000000000353261372272363700206640ustar00rootroot00000000000000# encoding: utf-8 """ Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import os, time, random import wx from .constants import * import Resources.CeciliaLib as CeciliaLib import Resources.PreferencePanel as PreferencePanel import Resources.CeciliaInterface as CeciliaInterface from .menubar import InterfaceMenuBar from .Widgets import * from .DocFrame import ManualFrame class CeciliaMainFrame(wx.Frame): def __init__(self, parent, ID): wx.Frame.__init__(self, parent, ID) self.menubar = InterfaceMenuBar(self, self) self.SetMenuBar(self.menubar) self.prefs = None self.time = 0 self.api_doc_frame = ManualFrame(kind="api") self.mod_doc_frame = ManualFrame(kind="modules") def setTime(self, curTime=0): self.time = curTime def updateTitle(self): title = os.path.split(CeciliaLib.getVar("currentCeciliaFile", unicode=True))[1] if CeciliaLib.getVar("interface"): CeciliaLib.getVar("interface").updateTitle('Cecilia5 - ' + title) def onShortPlayStop(self, event): self.onPlayStop(not CeciliaLib.getVar("audioServer").isAudioServerRunning()) def onPlayStop(self, value): if value: CeciliaLib.getControlPanel().nonZeroTime = 0 CeciliaLib.setVar("toDac", True) CeciliaLib.getVar("grapher").toolbar.loadingMsg.SetForegroundColour("#FFFFFF") CeciliaLib.getVar("grapher").toolbar.loadingMsg.Refresh() CeciliaLib.getControlPanel().transportButtons.setPlay(True) wx.CallLater(50, CeciliaLib.startCeciliaSound, True) else: CeciliaLib.stopCeciliaSound() def onBounceToDisk(self, event): CeciliaLib.getControlPanel().onBounceToDisk() def applyBatchProcessingFolder(self, value): folderName = value if folderName == "": return old_file_type = CeciliaLib.getVar("audioFileType") cfileins = CeciliaLib.getControlPanel().getCfileinList() num_snds = len(cfileins[0].fileMenu.choice) dlg = wx.ProgressDialog("Batch processing on sound folder", "", maximum=num_snds, parent=self, style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_SMOOTH) dlg.SetMinSize((600, -1)) dlg.SetClientSize((600, 100)) count = 0 totaltime = CeciliaLib.getVar("totalTime") for snd in cfileins[0].fileMenu.choice: cfileins[0].onSelectSound(-1, snd) if CeciliaLib.getVar("useSoundDur"): cfileins[0].setTotalTime() path = os.path.split(cfileins[0].filePath)[0] name, ext = os.path.splitext(snd) lext = ext.lower() if lext in [".wav", ".wave"]: CeciliaLib.setVar('audioFileType', "wav") elif lext in [".aif", ".aiff", ".aifc"]: CeciliaLib.setVar('audioFileType', "aif") elif lext in [".ogg"]: CeciliaLib.setVar('audioFileType', "ogg") elif lext in [".flac"]: CeciliaLib.setVar('audioFileType', "flac") elif lext in [".au"]: CeciliaLib.setVar('audioFileType', "au") elif lext in [".sd2"]: CeciliaLib.setVar('audioFileType', "sd2") elif lext in [".caf"]: CeciliaLib.setVar('audioFileType', "caf") if not os.path.isdir(os.path.join(path, folderName)): os.mkdir(os.path.join(path, folderName)) filename = os.path.join(path, folderName, "%s-%s%s" % (name, folderName, ext)) count += 1 (keepGoing, skip) = dlg.Update(count, "Exporting %s" % filename) CeciliaLib.getControlPanel().onBatchProcessing(filename) while (CeciliaLib.getVar("audioServer").isAudioServerRunning()): time.sleep(.1) if CeciliaLib.getVar("useSoundDur"): CeciliaLib.getControlPanel().setTotalTime(totaltime) CeciliaLib.getControlPanel().updateDurationSlider() dlg.Destroy() CeciliaLib.setVar('audioFileType', old_file_type) def applyBatchProcessingPreset(self, value): folderName = value if folderName == "": return cfileins = CeciliaLib.getControlPanel().getCfileinList() presets = CeciliaLib.getVar("presetPanel").getPresets() if "init" in presets: presets.remove("init") if "last save" in presets: presets.remove("last save") num_presets = len(presets) dlg = wx.ProgressDialog("Batch processing on preset sequence", "", maximum=num_presets, parent=self, style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_SMOOTH) dlg.SetMinSize((600, -1)) dlg.SetClientSize((600, 100)) if len(cfileins) > 0: filepath = cfileins[0].filePath count = 0 for preset in presets: CeciliaLib.loadPresetFromFile(preset) if len(cfileins) == 0: path = os.path.join(os.path.expanduser("~"), "Desktop") name = "batch" ext = "." + CeciliaLib.getVar("audioFileType") else: cfileins[0].onLoadFile(filepath) path, fname = os.path.split(cfileins[0].filePath) name, ext = os.path.splitext(fname) if not os.path.isdir(os.path.join(path, folderName)): os.mkdir(os.path.join(path, folderName)) filename = os.path.join(path, folderName, "%s-%s%s" % (name, preset, ext)) count += 1 (keepGoing, skip) = dlg.Update(count, "Exporting %s" % filename) CeciliaLib.getControlPanel().onBatchProcessing(filename) while (CeciliaLib.getVar("audioServer").isAudioServerRunning()): time.sleep(.1) dlg.Destroy() def onBatchProcessing(self, event): if event.GetId() == ID_BATCH_FOLDER: f = BatchPopupFrame(self, self.applyBatchProcessingFolder) else: f = BatchPopupFrame(self, self.applyBatchProcessingPreset) f.CenterOnScreen() f.Show() def onUseSoundDuration(self, evt): CeciliaLib.setVar("useSoundDur", evt.GetInt()) def onSelectOutputFilename(self): file = CeciliaLib.saveFileDialog(self, AUDIO_FILE_WILDCARD, type='Save audio') if file is not None: CeciliaLib.setVar("saveAudioFilePath", os.path.split(file)[0]) return file def closeInterface(self): if CeciliaLib.getVar("interface") is not None: CeciliaLib.getVar("interface").onClose(None) CeciliaLib.setVar("interface", None) def newRecent(self, file, remove=False): if ".cecilia5" in file: return file = CeciliaLib.ensureNFD(file) try: f = open(RECENT_FILE_PATH, "r") lines = [CeciliaLib.ensureNFD(line[:-1]) for line in f.readlines()] f.close() except: lines = [] update = False if not remove: if file not in lines and 'Resources/modules/' not in file: lines.insert(0, file) update = True else: if file in lines: lines.remove(file) update = True if update: f = open(RECENT_FILE_PATH, "w") if len(lines) > 10: lines = lines[0:10] for line in lines: f.write(line + '\n') f.close() subId2 = ID_OPEN_RECENT recentFiles = [] f = open(RECENT_FILE_PATH, "r") for line in f.readlines(): recentFiles.append(line) f.close() if recentFiles: for item in self.menubar.openRecentMenu.GetMenuItems(): self.menubar.openRecentMenu.Delete(item) for file in recentFiles: try: self.menubar.openRecentMenu.Append(subId2, file) subId2 += 1 except: pass def onOpen(self, event, builtin=False): if isinstance(event, wx.CommandEvent): CeciliaLib.openCeciliaFile(self) elif os.path.isfile(event): CeciliaLib.openCeciliaFile(self, event, builtin) def onOpenRandom(self, event): categories = [folder for folder in os.listdir(MODULES_PATH) if not folder.startswith(".")] category = random.choice(categories) files = [f for f in os.listdir(os.path.join(MODULES_PATH, category)) if f.endswith(FILE_EXTENSION)] file = random.choice(files) self.onOpen(os.path.join(MODULES_PATH, category, file), True) def openRecent(self, event): menu = self.GetMenuBar() id = event.GetId() file = menu.FindItemById(id).GetItemLabelText().replace("\n", "").strip() if os.path.isfile(file): CeciliaLib.openCeciliaFile(self, file) else: CeciliaLib.showErrorDialog("Error while trying to open a file!", "No such file : %s" % file[:-1]) self.newRecent(file, remove=True) def onOpenBuiltin(self, event): menu = self.GetMenuBar() id = event.GetId() file = menu.FindItemById(id) filename = file.GetItemLabelText() filedict = self.GetMenuBar().files for key in filedict.keys(): if filename in filedict[key]: dirname = key break name = os.path.join(CeciliaLib.ensureNFD(MODULES_PATH), dirname, filename) CeciliaLib.openCeciliaFile(self, name, True) def onOpenPrefModule(self, event): menu = self.GetMenuBar() id = event.GetId() file = menu.FindItemById(id) filename = file.GetItemLabelText() filedir = file.GetMenu().GetTitle() prefPath = CeciliaLib.getVar("prefferedPath") prefPaths = prefPath.split(';') prefBaseNames = [os.path.basename(path) for path in prefPaths] dirname = prefPaths[prefBaseNames.index(filedir)] if dirname: name = os.path.join(dirname, filename) CeciliaLib.openCeciliaFile(self, name) def openModuleAsText(self, event): CeciliaLib.openCurrentFileAsText(CeciliaLib.getVar("currentCeciliaFile")) def reloadCurrentModule(self, event): CeciliaLib.openCeciliaFile(self, CeciliaLib.getVar("currentCeciliaFile")) def onSaveAs(self, event): CeciliaLib.saveCeciliaFile(self) self.updateTitle() def onPreferences(self, event): self.prefs = PreferencePanel.PreferenceFrame(self) self.prefs.Show() self.prefs.Center() def onRememberInputSound(self, event): CeciliaLib.getVar("interface").menubar.editMenu.FindItemById(ID_REMEMBER).Check(event.GetInt()) CeciliaLib.setVar("rememberedSound", event.GetInt()) return def onUpdateInterface(self, event): if event is not None: snds = [] if CeciliaLib.getVar("rememberedSound"): for key in CeciliaLib.getVar("userInputs").keys(): if CeciliaLib.getVar("userInputs")[key]['path'] != '': snds.append(CeciliaLib.getVar("userInputs")[key]['path']) if CeciliaLib.getVar("audioServer").isAudioServerRunning(): CeciliaLib.stopCeciliaSound() self.closeInterface() CeciliaLib.parseInterfaceText() title = os.path.split(CeciliaLib.getVar("currentCeciliaFile", unicode=True))[1] ceciliaInterface = CeciliaInterface.CeciliaInterface(None, title='Interface - %s' % title, mainFrame=self) ceciliaInterface.SetSize(CeciliaLib.getVar("interfaceSize")) ceciliaInterface.SetPosition(CeciliaLib.getVar("interfacePosition")) CeciliaLib.setVar("interface", ceciliaInterface) CeciliaLib.getVar("presetPanel").loadPresets() if event is not None: for i, cfilein in enumerate(CeciliaLib.getControlPanel().getCfileinList()): if i >= len(snds): break cfilein.onLoadFile(snds[i]) def onShowSpectrum(self, event): CeciliaLib.setVar('showSpectrum', event.GetInt()) def openSpectrumWindow(self): if CeciliaLib.getVar('spectrumFrame') is None: f = SpectrumFrame(CeciliaLib.getVar("interface")) f.setAnalyzer(CeciliaLib.getVar("audioServer").spectrum) f.Center() f.Show() CeciliaLib.setVar('spectrumFrame', f) def onQuit(self, event): reallyQuit = False msg = "Do you really want to quit Cecilia ?" dlg = wx.MessageDialog(self, msg, "Quit Cecilia5...", style=wx.YES_NO | wx.STAY_ON_TOP) ret = dlg.ShowModal() if ret == wx.ID_YES: reallyQuit = True dlg.Destroy() if not reallyQuit: return try: self.prefs.onClose(event) except: pass if CeciliaLib.getVar("audioServer").isAudioServerRunning(): CeciliaLib.getVar("audioServer").stop() time.sleep(.2) if CeciliaLib.getVar('spectrumFrame') is not None: try: CeciliaLib.getVar('spectrumFrame')._destroy(None) except: pass finally: CeciliaLib.setVar('spectrumFrame', None) self.api_doc_frame.Destroy() self.mod_doc_frame.Destroy() CeciliaLib.closeCeciliaFile(self) CeciliaLib.writeVarToDisk() self.Destroy() def onUseMidi(self, event): CeciliaLib.setVar("useMidi", event.GetInt()) def onHelpAbout(self, evt): Y = CeciliaLib.getVar("displaySize")[0][1] about = AboutPopupFrame(self, Y // 5) about.Show() def onModuleAbout(self, evt): file = os.path.split(CeciliaLib.getVar("currentCeciliaFile"))[1] self.mod_doc_frame.Center() self.mod_doc_frame.openPage(file) def onDocFrame(self, evt): self.api_doc_frame.Center() self.api_doc_frame.Show() def onGraphFrame(self, evt): self.graph_doc_frame = wx.MessageDialog(self, TT_GRAPHER) self.graph_doc_frame.ShowModal() self.graph_doc_frame.Destroy() cecilia5-5.4.1/Resources/CeciliaPlot.py000066400000000000000000001747111372272363700177420ustar00rootroot00000000000000# encoding: utf-8 #----------------------------------------------------------------------------- # Name: wx.lib.plot.py # Purpose: Line, Bar and Scatter Graphs # # Author: Gordon Williams # # Created: 2003/11/03 # RCS-ID: $Id: plot.py 51004 2008-01-03 08:17:39Z RD $ # Copyright: (c) 2002 # Licence: Use as you wish. #----------------------------------------------------------------------------- # 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o 2.5 compatability update. # o Renamed to plot.py in the wx.lib directory. # o Reworked test frame to work with wx demo framework. This saves a bit # of tedious cut and paste, and the test app is excellent. # # 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o wxScrolledMessageDialog -> ScrolledMessageDialog # # Oct 6, 2004 Gordon Williams (g_will@cyberus.ca) # - Added bar graph demo # - Modified line end shape from round to square. # - Removed FloatDCWrapper for conversion to ints and ints in arguments # # Oct 15, 2004 Gordon Williams (g_will@cyberus.ca) # - Imported modules given leading underscore to name. # - Added Cursor Line Tracking and User Point Labels. # - Demo for Cursor Line Tracking and Point Labels. # - Size of plot preview frame adjusted to show page better. # - Added helper functions PositionUserToScreen and PositionScreenToUser in PlotCanvas. # - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve) # can be in either user coords or screen coords. # # """ This is a simple light weight plotting module that can be used with Boa or easily integrated into your own wxPython application. The emphasis is on small size and fast plotting for large data sets. It has a reasonable number of features to do line and scatter graphs easily as well as simple bar graphs. It is not as sophisticated or as powerful as SciPy Plt or Chaco. Both of these are great packages but consume huge amounts of computer resources for simple plots. They can be found at http://scipy.com This file contains two parts; first the re-usable library stuff, then, after a "if __name__=='__main__'" test, a simple frame and a few default plots for examples and testing. Based on wxPlotCanvas Written by K.Hinsen, R. Srinivasan; Ported to wxPython Harm van der Heijden, feb 1999 Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca) -More style options -Zooming using mouse "rubber band" -Scroll left, right -Grid(graticule) -Printing, preview, and page set up (margins) -Axis and title labels -Cursor xy axis values -Doc strings and lots of comments -Optimizations for large number of points -Legends Did a lot of work here to speed markers up. Only a factor of 4 improvement though. Lines are much faster than markers, especially filled markers. Stay away from circles and triangles unless you only have a few thousand points. Times for 25, 000 points Line - 0.078 sec Markers Square - 0.22 sec dot - 0.10 circle - 0.87 cross, plus - 0.28 triangle, triangle_down - 0.90 Thanks to Chris Barker for getting this version working on Linux. Zooming controls with mouse (when enabled): Left mouse drag - Zoom box. Left mouse double click - reset zoom. Right mouse click - zoom out centred on click location. """ import wx import Resources.CeciliaLib as CeciliaLib from .constants import * try: import numpy as _Numeric except: msg = """ This module requires the NumPy module, which could not be imported. It probably is not installed (it's not part of the standard Python distribution). See the NumPy Python site (http://www.numpy.org/) for information on downloading source or binaries.""" raise ImportError("NumPy not found. \n" + msg) class PolyPoints: """Base Class for lines and markers - All methods are private. """ def __init__(self, points, attr): self._points = _Numeric.array(points).astype(_Numeric.float32) self._logscale = (False, False) self.currentScale = (1, 1) self.currentShift = (0, 0) self.scaled = self.points self.attributes = {} self.attributes.update(self._attributes) for name, value in attr.items(): if name not in self._attributes.keys(): raise KeyError("Style attribute incorrect. Should be one of %s" % list(self._attributes.keys())) self.attributes[name] = value def setLogScale(self, logscale): self._logscale = logscale def __getattr__(self, name): if name == 'points': if len(self._points) > 0: data = _Numeric.array(self._points, copy=True) if self._logscale[0]: data = self.log10(data, 0) if self._logscale[1]: data = self.log10(data, 1) return data else: return self._points else: raise AttributeError(name) def log10(self, data, ind): data = _Numeric.compress(data[:, ind] > 0, data, 0) data[:, ind] = _Numeric.log10(data[:, ind]) return data def boundingBox(self): if len(self.points) == 0: # no curves to draw # defaults to (-1, -1) and (1, 1) but axis can be set in Draw minXY = _Numeric.array([-1.0, -1.0]) maxXY = _Numeric.array([1.0, 1.0]) else: minXY = _Numeric.minimum.reduce(self.points) maxXY = _Numeric.maximum.reduce(self.points) return minXY, maxXY def scaleAndShift(self, scale=(1, 1), shift=(0, 0)): if len(self.points) == 0: # no curves to draw return if (scale is not self.currentScale) or (shift is not self.currentShift): # update point scaling self.scaled = scale * self.points + shift self.currentScale = scale self.currentShift = shift def getLegend(self): return self.attributes['legend'] def getColour(self): return self.attributes['colour'] def getClosestPoint(self, pntXY, pointScaled=True): """Returns the index of closest point on the curve, pointXY, scaledXY, distance x, y in user coords if pointScaled == True based on screen coords if pointScaled == False based on user coords """ if pointScaled: #Using screen coords p = self.scaled pxy = self.currentScale * _Numeric.array(pntXY) + self.currentShift else: #Using user coords p = self.points pxy = _Numeric.array(pntXY) #determine distance for each point d = _Numeric.sqrt(_Numeric.add.reduce((p - pxy) ** 2, 1)) #sqrt(dx^2+dy^2) pntIndex = _Numeric.argmin(d) dist = d[pntIndex] return [pntIndex, self.points[pntIndex], self.scaled[pntIndex], dist] class PolyLine(PolyPoints): """Class to define line type and style - All methods except __init__ are private. """ _attributes = {'colour': 'black', 'width': 1, 'style': wx.SOLID, 'legend': ''} def __init__(self, points, **attr): """Creates PolyLine object points - sequence (array, tuple or list) of (x, y) points making up line **attr - key word attributes Defaults: 'colour'= 'black', - wx.Pen Colour any wx.Colour 'width'= 1, - Pen width 'style'= wx.SOLID, - wx.Pen style 'legend'= '' - Line Legend to display """ PolyPoints.__init__(self, points, attr) def draw(self, gc, coord=None): colour = self.attributes['colour'] width = self.attributes['width'] style = self.attributes['style'] if not isinstance(colour, wx.Colour): colour = wx.Colour(colour) pen = wx.Pen(colour, width, style) pen.SetCap(wx.CAP_BUTT) gc.SetPen(pen) if coord is None: if len(self.scaled) >= 2: gc.DrawLines([[x[0], x[1]] for x in self.scaled]) else: gc.DrawLines(coord.tolist()) # draw legend line, not used in Cecilia def getSymExtent(self): """Width and Height of Marker""" h = self.attributes['width'] w = 5 * h return (w, h) def GetCircleBitmap(w=6, h=6, fillcol="#000000", pencol="#000000"): maskColour = "#CCCCCC" b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) dc.SetBrush(wx.Brush(maskColour)) dc.SetPen(wx.Pen(maskColour)) dc.Clear() rec = wx.Rect(0, 0, w, h) dc.DrawRectangle(rec) dc.SetBrush(wx.Brush(fillcol, wx.SOLID)) dc.SetPen(wx.Pen(pencol, 1, wx.SOLID)) dc.DrawEllipse(0, 0, w, h) dc.SelectObject(wx.NullBitmap) b.SetMaskColour(maskColour) return b class PolyMarker(PolyPoints): """Class to define marker type and style - All methods except __init__ are private. """ _attributes = {'colour': 'black', 'width': 1, 'size': 2, 'fillcolour': None, 'fillstyle': wx.SOLID, 'marker': 'circle', 'legend': ''} def __init__(self, points, **attr): """Creates PolyMarker object points - sequence (array, tuple or list) of (x, y) points **attr - key word attributes Defaults: 'colour'= 'black', - wx.Pen Colour any wx.Colour 'width'= 1, - Pen width 'size'= 2, - Marker size 'fillcolour'= same as colour, - wx.Brush Colour any wx.Colour 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill) 'marker'= 'circle' - Marker shape 'legend'= '' - Marker Legend to display Marker Shapes: - 'circle' - 'dot' - 'square' - 'triangle' - 'triangle_down' - 'cross' - 'plus' - 'bmp' ---> Cecilia 5 grapher marker - 'bmpsel' ---> Cecilia 5 grapher selected marker - 'none' ---> Cecilia 5 grapher non selected lines """ PolyPoints.__init__(self, points, attr) self.circleBitmap = GetCircleBitmap(6, 6, "#000000", "#000000") self.circleBitmapSel = GetCircleBitmap(8, 8, "#EEEEEE", "#000000") def draw(self, gc, coord=None): colour = self.attributes['colour'] width = self.attributes['width'] size = self.attributes['size'] fillcolour = self.attributes['fillcolour'] fillstyle = self.attributes['fillstyle'] marker = self.attributes['marker'] if colour and not isinstance(colour, wx.Colour): colour = wx.Colour(colour) if fillcolour and not isinstance(fillcolour, wx.Colour): fillcolour = wx.Colour(fillcolour) gc.SetPen(wx.Pen(colour, width)) if fillcolour: gc.SetBrush(wx.Brush(fillcolour, fillstyle)) else: gc.SetBrush(wx.Brush(colour, fillstyle)) if coord is None: self._drawmarkers(gc, self.scaled, marker, size) else: self._drawmarkers(gc, coord, marker, size) # draw legend marker def getSymExtent(self): """Width and Height of Marker""" s = 5 * self.attributes['size'] return (s, s) def _drawmarkers(self, gc, coords, marker, size=1): f = eval('self._' + marker) f(gc, coords, size) def _bmp(self, gc, coords, size=1): path = gc.CreatePath() path.AddCircle(0, 0, 3) gc.PushState() last = (0, 0) for c in coords: dx, dy = c[0] - last[0], c[1] - last[1] gc.Translate(dx, dy) gc.FillPath(path) last = c gc.PopState() def _bmpsel(self, gc, coords, size=1): path = gc.CreatePath() path.AddCircle(0, 0, 3.5) gc.PushState() last = (0, 0) for c in coords: dx, dy = c[0] - last[0], c[1] - last[1] gc.Translate(dx, dy) gc.DrawPath(path) last = c gc.PopState() def _none(self, gc, coords, size=1): pass class PlotGraphics: """Container to hold PolyXXX objects and graph labels - All methods except __init__ are private. """ def __init__(self, objects, title='', xLabel='', yLabel=''): """Creates PlotGraphics object objects - list of PolyXXX objects to make graph title - title shown at top of graph xLabel - label shown on x-axis yLabel - label shown on y-axis """ if type(objects) not in [list, tuple]: raise TypeError("objects argument should be list or tuple.") self.objects = objects self.title = title self.xLabel = xLabel self.yLabel = yLabel def setLogScale(self, logscale): if type(logscale) != tuple: raise TypeError('logscale must be a tuple of bools, e.g. (False, False).') if len(self.objects) == 0: return for o in self.objects: o.setLogScale(logscale) def boundingBox(self): p1, p2 = self.objects[0].boundingBox() for o in self.objects[1:]: p1o, p2o = o.boundingBox() p1 = _Numeric.minimum(p1, p1o) p2 = _Numeric.maximum(p2, p2o) return p1, p2 def scaleAndShift(self, scale=(1, 1), shift=(0, 0)): for o in self.objects: o.scaleAndShift(scale, shift) def setXLabel(self, xLabel=''): """Set the X axis label on the graph""" self.xLabel = xLabel def setYLabel(self, yLabel=''): """Set the Y axis label on the graph""" self.yLabel = yLabel def setTitle(self, title=''): """Set the title at the top of graph""" self.title = title def getXLabel(self): """Get x axis label string""" return self.xLabel def getYLabel(self): """Get y axis label string""" return self.yLabel def getTitle(self, title=''): """Get the title at the top of graph""" return self.title def draw(self, gc): for o in self.objects: o.draw(gc) def getSymExtent(self): """Get max width and height of lines and markers symbols for legend""" symExt = self.objects[0].getSymExtent() for o in self.objects[1:]: oSymExt = o.getSymExtent() symExt = _Numeric.maximum(symExt, oSymExt) return symExt def getLegendNames(self): """Returns list of legend names""" lst = [None] * len(self) for i in range(len(self)): lst[i] = self.objects[i].getLegend() return lst def __len__(self): return len(self.objects) def __getitem__(self, item): return self.objects[item] def GetRectMask(sw, sh, w, h): maskColor = wx.Colour(0, 0, 0) shownColor = wx.Colour(255, 255, 255) b = wx.EmptyBitmap(sw, sh) dc = wx.MemoryDC(b) dc.SetBrush(wx.Brush(maskColor)) dc.SetPen(wx.Pen(maskColor)) dc.DrawRectangle(0, 0, sw, sh) dc.SetBrush(wx.Brush(shownColor)) dc.SetPen(wx.Pen(shownColor)) dc.DrawRectangle(0, 0, w, h) dc.SelectObject(wx.NullBitmap) b.SetMaskColour(maskColor) return b #------------------------------------------------------------------------------- # Main window that you will want to import into your application. class PlotCanvas(wx.Panel): """ Subclass of a wx.Panel which holds two scrollbars and the actual plotting canvas (self.canvas). It allows for simple general plotting of data with zoom, labels, and automatic axis scaling.""" def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, name="plotCanvas"): """Constructs a panel, which can be a child of a frame or any other non-control window""" wx.Panel.__init__(self, parent, id, pos, size, style, name) sizer = wx.FlexGridSizer(2, 2, 0, 0) self.canvas = wx.Window(self, size=parent.GetClientSize()) self.sb_vert = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL) self.sb_vert.SetScrollbar(0, 1000, 1000, 1000) self.sb_hor = wx.ScrollBar(self, -1, style=wx.SB_HORIZONTAL) self.sb_hor.SetScrollbar(0, 1000, 1000, 1000) sizer.Add(self.canvas, 1, wx.EXPAND) sizer.Add(self.sb_vert, 0, wx.EXPAND) sizer.Add(self.sb_hor, 0, wx.EXPAND) sizer.Add((0, 0)) sizer.AddGrowableRow(0, 1) sizer.AddGrowableCol(0, 1) self.sb_vert.Show(False) self.sb_hor.Show(False) self.SetSizer(sizer) self.Fit() self.border = (1, 1) self.SetBackgroundColour("white") # Create some mouse events for zooming self.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown) self.canvas.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp) self.canvas.Bind(wx.EVT_MOTION, self.OnMotion) self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick) self.canvas.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown) # scrollbar events self.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnScroll) self.Bind(wx.EVT_SCROLL_PAGEUP, self.OnScroll) self.Bind(wx.EVT_SCROLL_PAGEDOWN, self.OnScroll) self.Bind(wx.EVT_SCROLL_LINEUP, self.OnScroll) self.Bind(wx.EVT_SCROLL_LINEDOWN, self.OnScroll) # set curser as cross-hairs self.ArrowCursor = wx.Cursor(wx.CURSOR_ARROW) self.canvas.SetCursor(self.ArrowCursor) self.PencilCursor = wx.Cursor(wx.CURSOR_PENCIL) self.HandCursor = wx.Cursor(wx.CURSOR_HAND) self.GrabHandCursor = wx.Cursor(wx.CURSOR_HAND) self.MagCursor = wx.Cursor(wx.CURSOR_MAGNIFIER) # scrollbar variables self._sb_ignore = False self._adjustingSB = False self._sb_xfullrange = 0 self._sb_yfullrange = 0 self._sb_xunit = 0 self._sb_yunit = 0 self._dragEnabled = False self._screenCoordinates = _Numeric.array([0.0, 0.0]) self._logscale = (False, False) self._background_bitmap = None self._oldSize = wx.Size(0, 0) # Zooming variables self._zoomInFactor = 0.5 self._zoomOutFactor = 2 self._zoomCorner1 = _Numeric.array([0.0, 0.0]) # left mouse down corner self._zoomCorner2 = _Numeric.array([0.0, 0.0]) # left mouse up corner self._zoomEnabled = False self._hasDragged = False # Drawing Variables self.last_draw = None self._pointScale = 1 self._pointShift = 0 self._xSpec = 'auto' self._ySpec = 'auto' self._gridEnabled = False self._legendEnabled = False self._titleEnabled = True self._clientSize = (0, 0) # Fonts self._fontCache = {} self._fontSizeAxis = 9 self._fontSizeTitle = 15 self._fontSizeLegend = 10 # Values to print on graph self._posToDrawValues = (0, 0) self._Xvalue = '' self._Yvalue = '' # corners from selection rect self._selectionCorner1 = None self._selectionCorner2 = None # current time value self.time = 0 # pointLabels self._pointLabelEnabled = False self.last_PointLabel = None self._pointLabelFunc = None self.canvas.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) self._useScientificNotation = False self.canvas.Bind(wx.EVT_PAINT, self.OnPaint) self.canvas.Bind(wx.EVT_SIZE, self.OnSize) self._isWindowCreated = False if '__WXGTK__' in wx.PlatformInfo: self.canvas.Bind(wx.EVT_WINDOW_CREATE, self.doOnSize) else: self.doOnSize() self._gridColour = wx.Colour('black') def doOnSize(self, evt=None): self._isWindowCreated = True self.OnSize(None) # sets the initial size based on client size def setValuesToDraw(self, pos, x=None, y=None): self._posToDrawValues = pos if x is not None: self._Xvalue = 'X: %.3f' % x self._Yvalue = 'Y: %.3f' % y else: self._Xvalue = '' self._Yvalue = '' def drawSelectionRect(self, c1, c2): self._selectionCorner1 = c1 self._selectionCorner2 = c2 def SetCursor(self, cursor): self.canvas.SetCursor(cursor) def GetGridColour(self): return self._gridColour def SetGridColour(self, colour): if isinstance(colour, wx.Colour): self._gridColour = colour else: self._gridColour = wx.Colour(colour) def SetBackColour(self, colour): if isinstance(colour, wx.Colour): self._backColour = colour else: self._backColour = wx.Colour(colour) def setBackgroundBitmap(self, bit): self._background_bitmap = bit def setLogScale(self, logscale): if type(logscale) != tuple: raise TypeError('logscale must be a tuple of bools, e.g. (False, False).') if self.last_draw is not None: graphics, xAxis, yAxis = self.last_draw graphics.setLogScale(logscale) self.last_draw = (graphics, None, None) self.SetXSpec('min') self.SetYSpec('min') self._logscale = logscale def getLogScale(self): return self._logscale def SetFontSizeAxis(self, point=10): """Set the tick and axis label font size (default is 10 point)""" self._fontSizeAxis = point def GetFontSizeAxis(self): """Get current tick and axis label font size in points""" return self._fontSizeAxis def SetFontSizeTitle(self, point=15): """Set Title font size (default is 15 point)""" self._fontSizeTitle = point def GetFontSizeTitle(self): """Get current Title font size in points""" return self._fontSizeTitle def SetFontSizeLegend(self, point=7): """Set Legend font size (default is 7 point)""" self._fontSizeLegend = point def GetFontSizeLegend(self): """Get current Legend font size in points""" return self._fontSizeLegend def SetShowScrollbars(self, value): """Set True to show scrollbars""" if value not in [True, False]: raise TypeError("Value should be True or False.") if value == self.GetShowScrollbars(): return self.sb_vert.Show(value) self.sb_hor.Show(value) wx.CallAfter(self.Layout) def GetShowScrollbars(self): """Set True to show scrollbars""" return self.sb_vert.IsShown() def SetUseScientificNotation(self, useScientificNotation): self._useScientificNotation = useScientificNotation def GetUseScientificNotation(self): return self._useScientificNotation def SetToolCursor(self, tool): if tool == 0: self.SetCursor(self.ArrowCursor) elif tool == 1: self.SetCursor(self.PencilCursor) def SetEnableDrag(self, value, tool=0): """Set True to enable drag.""" if value not in [True, False]: raise TypeError("Value should be True or False.") if value: if self.GetEnableZoom(): self.SetEnableZoom(False) self.SetCursor(self.HandCursor) else: if tool == 0: self.SetCursor(self.ArrowCursor) self._dragEnabled = value def GetEnableDrag(self): return self._dragEnabled def SetEnableZoom(self, value, tool=0): """Set True to enable zooming.""" if value not in [True, False]: raise TypeError("Value should be True or False.") if value: if self.GetEnableDrag(): self.SetEnableDrag(False) self.SetCursor(self.MagCursor) else: if tool == 0: self.SetCursor(self.ArrowCursor) self._zoomEnabled = value def GetEnableZoom(self): """True if zooming enabled.""" return self._zoomEnabled def SetEnableGrid(self, value): """Set True to enable grid.""" if value not in [True, False, 'Horizontal', 'Vertical']: raise TypeError("Value should be True, False, Horizontal or Vertical.") self._gridEnabled = value self.Redraw() def GetEnableGrid(self): """True if grid enabled.""" return self._gridEnabled def SetEnableLegend(self, value): """Set True to enable legend.""" if value not in [True, False]: raise TypeError("Value should be True or False.") self._legendEnabled = value self.Redraw() def GetEnableLegend(self): """True if Legend enabled.""" return self._legendEnabled def SetEnableTitle(self, value): """Set True to enable title.""" if value not in [True, False]: raise TypeError("Value should be True or False.") self._titleEnabled = value self.Redraw() def GetEnableTitle(self): """True if title enabled.""" return self._titleEnabled def SetEnablePointLabel(self, value): """Set True to enable pointLabel.""" if value not in [True, False]: raise TypeError("Value should be True or False.") self._pointLabelEnabled = value self.Redraw() #will erase existing pointLabel if present self.last_PointLabel = None def GetEnablePointLabel(self): """True if pointLabel enabled.""" return self._pointLabelEnabled def SetPointLabelFunc(self, func): """Sets the function with custom code for pointLabel drawing ******** more info needed *************** """ self._pointLabelFunc = func def GetPointLabelFunc(self): """Returns pointLabel Drawing Function""" return self._pointLabelFunc def Reset(self): """Unzoom the plot.""" self.last_PointLabel = None #reset pointLabel if self.last_draw is not None: self._Draw(self.last_draw[0]) def ScrollRight(self, units): """Move view right number of axis units.""" self.last_PointLabel = None #reset pointLabel if self.last_draw is not None: graphics, xAxis, yAxis = self.last_draw xAxis = (xAxis[0] + units, xAxis[1] + units) self._Draw(graphics, xAxis, yAxis) def ScrollUp(self, units): """Move view up number of axis units.""" self.last_PointLabel = None #reset pointLabel if self.last_draw is not None: graphics, xAxis, yAxis = self.last_draw yAxis = (yAxis[0] + units, yAxis[1] + units) self._Draw(graphics, xAxis, yAxis) def GetXY(self, event): """Wrapper around _getXY, which handles log scales""" x, y = self._getXY(event) if self.getLogScale()[0]: x = _Numeric.power(10, x) if self.getLogScale()[1]: y = _Numeric.power(10, y) return x, y def _getXY(self, event): """Takes a mouse event and returns the XY user axis values.""" x, y = self.PositionScreenToUser(event.GetPosition()) return x, y def PositionUserToScreen(self, pntXY): """Converts User position to Screen Coordinates""" userPos = _Numeric.array(pntXY) x, y = userPos * self._pointScale + self._pointShift return x, y def PositionScreenToUser(self, pntXY): """Converts Screen position to User Coordinates""" screenPos = _Numeric.array(pntXY) x, y = (screenPos - self._pointShift) / self._pointScale return x, y def SetXSpec(self, type='auto'): """xSpec- defines x axis type. Can be 'none', 'min' or 'auto' where: 'none' - shows no axis or tick mark values 'min' - shows min bounding box values 'auto' - rounds axis range to sensible values """ self._xSpec = type def SetYSpec(self, type='auto'): """ySpec- defines x axis type. Can be 'none', 'min' or 'auto' where: 'none' - shows no axis or tick mark values 'min' - shows min bounding box values 'auto' - rounds axis range to sensible values """ self._ySpec = type def GetXSpec(self): """Returns current XSpec for axis""" return self._xSpec def GetYSpec(self): """Returns current YSpec for axis""" return self._ySpec def GetXMaxRange(self): xAxis = self._getXMaxRange() if self.getLogScale()[0]: xAxis = _Numeric.power(10, xAxis) return xAxis def _getXMaxRange(self): """Returns (minX, maxX) x-axis range for displayed graph""" graphics = self.last_draw[0] p1, p2 = graphics.boundingBox() # min, max points of graphics xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units return xAxis def GetYMaxRange(self): yAxis = self._getYMaxRange() if self.getLogScale()[1]: yAxis = _Numeric.power(10, yAxis) return yAxis def _getYMaxRange(self): """Returns (minY, maxY) y-axis range for displayed graph""" graphics = self.last_draw[0] p1, p2 = graphics.boundingBox() # min, max points of graphics yAxis = self._axisInterval(self._ySpec, p1[1], p2[1]) return yAxis def GetXCurrentRange(self): xAxis = self._getXCurrentRange() if self.getLogScale()[0]: xAxis = _Numeric.power(10, xAxis) return xAxis def _getXCurrentRange(self): """Returns (minX, maxX) x-axis for currently displayed portion of graph""" return self.last_draw[1] def GetYCurrentRange(self): yAxis = self._getYCurrentRange() if self.getLogScale()[1]: yAxis = _Numeric.power(10, yAxis) return yAxis def _getYCurrentRange(self): """Returns (minY, maxY) y-axis for currently displayed portion of graph""" return self.last_draw[2] def Draw(self, graphics, xAxis=None, yAxis=None, dc=None): """Wrapper around _Draw, which handles log axes""" graphics.setLogScale(self.getLogScale()) # check Axis is either tuple or none if type(xAxis) not in [type(None), tuple]: raise TypeError("xAxis should be None or (minX, maxX)." + str(type(xAxis))) if type(yAxis) not in [type(None), tuple]: raise TypeError("yAxis should be None or (minY, maxY)." + str(type(xAxis))) # check case for axis = (a, b) where a==b caused by improper zooms if xAxis is not None: if xAxis[0] == xAxis[1]: return if self.getLogScale()[0]: xAxis = _Numeric.log10(xAxis) if yAxis is not None: if yAxis[0] == yAxis[1]: return if self.getLogScale()[1]: yAxis = _Numeric.log10(yAxis) self._Draw(graphics, xAxis, yAxis, dc) def _Draw(self, graphics, xAxis=None, yAxis=None, dc=None): """ Draw objects in graphics with specified x and y axis. graphics- instance of PlotGraphics with list of PolyXXX objects xAxis - tuple with (min, max) axis range to view yAxis - same as xAxis dc - drawing context - doesn't have to be specified. If it's not, the offscreen buffer is used """ if self._zoomed: minX, minY = _Numeric.minimum(self._zoomCorner1, self._zoomCorner2) maxX, maxY = _Numeric.maximum(self._zoomCorner1, self._zoomCorner2) xAxis = (minX, maxX) yAxis = (minY, maxY) # sizes axis to axis type, create lower left and upper right corners of plot if xAxis is None or yAxis is None: # One or both axis not specified in Draw p1, p2 = graphics.boundingBox() # min, max points of graphics if xAxis is None: xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units if yAxis is None: yAxis = self._axisInterval(self._ySpec, p1[1], p2[1]) # Adjust bounding box for axis spec p1[0], p1[1] = xAxis[0], yAxis[0] # lower left corner user scale (xmin, ymin) p2[0], p2[1] = xAxis[1], yAxis[1] # upper right corner user scale (xmax, ymax) else: # Both axis specified in Draw p1 = _Numeric.array([xAxis[0], yAxis[0]]) # lower left corner user scale (xmin, ymin) p2 = _Numeric.array([xAxis[1], yAxis[1]]) # upper right corner user scale (xmax, ymax) self.last_draw = (graphics, _Numeric.array(xAxis), _Numeric.array(yAxis)) # saves most recient values if dc is None: # sets new dc and clears it dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer) dc.Clear() gc = wx.GraphicsContext_Create(dc) # set font size for every thing but title and legend dc.SetFont(self._getFont(self._fontSizeAxis)) # Get ticks and textExtents for axis if required if self._xSpec is not 'none': xticks = self._xticks(xAxis[0], xAxis[1]) xTextExtent = dc.GetTextExtent(xticks[-1][1])# w h of x axis text last number on axis else: xticks = None xTextExtent = (0, 0) # No text for ticks if self._ySpec is not 'none': yticks = self._yticks(yAxis[0], yAxis[1]) if self.getLogScale()[1]: yTextExtent = dc.GetTextExtent('-2e-2') else: yTextExtentBottom = dc.GetTextExtent(yticks[0][1]) yTextExtentTop = dc.GetTextExtent(yticks[-1][1]) yTextExtent = (max(yTextExtentBottom[0], yTextExtentTop[0]), max(yTextExtentBottom[1], yTextExtentTop[1])) else: yticks = None yTextExtent = (0, 0) # No text for ticks # TextExtents for Title and Axis Labels titleWH, xLabelWH, yLabelWH = self._titleLablesWH(dc, graphics) # TextExtents for Legend legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics) # room around graph area rhsW = xTextExtent[0] #max(xTextExtent[0], legendBoxWH[0]) # use larger of number width or legend width lhsW = yTextExtent[0] + yLabelWH[1] bottomH = max(xTextExtent[1], yTextExtent[1] / 2.) + xLabelWH[1] if wx.Platform == '__WXMAC__': topH = 0 textSize_scale = _Numeric.array([rhsW + lhsW, 8]) #_Numeric.array([rhsW+lhsW, bottomH+topH]) # make plot area smaller by text size else: topH = yTextExtent[1] / 2. + titleWH[1] textSize_scale = _Numeric.array([rhsW + lhsW, 15]) # make plot area smaller by text size textSize_shift = _Numeric.array([lhsW, bottomH]) # shift plot area by this amount # draw title if requested if self._titleEnabled: dc.SetFont(self._getFont(self._fontSizeTitle)) titlePos = (self.plotbox_origin[0] + lhsW + (self.plotbox_size[0] - lhsW - rhsW) / 2. - titleWH[0] / 2., self.plotbox_origin[1] - self.plotbox_size[1]) dc.DrawText(graphics.getTitle(), titlePos[0], titlePos[1]) # draw label text dc.SetFont(self._getFont(self._fontSizeAxis)) xLabelPos = (self.plotbox_origin[0] + lhsW + (self.plotbox_size[0] - lhsW - rhsW) / 2. - xLabelWH[0] / 2., self.plotbox_origin[1] - xLabelWH[1]) dc.DrawText(graphics.getXLabel(), xLabelPos[0], xLabelPos[1]) yLabelPos = (self.plotbox_origin[0], self.plotbox_origin[1] - bottomH - (self.plotbox_size[1] - bottomH - topH) / 2. + yLabelWH[0] / 2.) if graphics.getYLabel(): # bug fix for Linux dc.DrawRotatedText(graphics.getYLabel(), yLabelPos[0], yLabelPos[1], 90) # drawing legend makers and text if self._legendEnabled: self._drawLegend(dc, graphics, rhsW, topH, legendBoxWH, legendSymExt, legendTextExt) # allow for scaling and shifting plotted points scale = (self.plotbox_size - textSize_scale) / (p2 - p1) * _Numeric.array((1, -1)) shift = -p1 * scale + self.plotbox_origin + textSize_shift * _Numeric.array((1, -1)) self._pointScale = scale # make available for mouse events self._pointShift = shift size = dc.GetSize() dc.SetPen(wx.Pen(self._backColour, 1)) dc.SetBrush(wx.Brush(self._backColour)) dc.DrawRectangle(0, 0, size[0], size[1]) ptx, pty, rectWidth, rectHeight = self._point2ClientCoord(p1, p2) if self._background_bitmap is not None and CeciliaLib.getVar("graphTexture"): if size != self._oldSize: self._scaled_background_bitmap = self._background_bitmap.GetSubBitmap(wx.Rect(0, 0, rectWidth, rectHeight)) self._oldSize = size dc.DrawBitmap(self._scaled_background_bitmap, ptx, pty) self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks) graphics.scaleAndShift(scale, shift) # set clipping area so drawing does not occur outside axis box ptx, pty, rectWidth, rectHeight = self._point2ClientCoord(p1, p2) dc.SetClippingRegion(ptx - 5, pty - 5, rectWidth + 10, rectHeight + 10) # Draw the lines and markers graphics.draw(gc) # Draw position values on graph ------------------------------ pos1, pos2 = self._onePoint2ClientCoord(self._posToDrawValues) dc.DrawText(self._Xvalue, pos1, pos2 - 30) dc.DrawText(self._Yvalue, pos1, pos2 - 20) # Draw selection marquee ------------------------------ if self._selectionCorner1 is not None: x, y, w, h = self._point2ClientCoord(self._selectionCorner1, self._selectionCorner2) dc.SetPen(wx.Pen(wx.BLACK)) dc.SetBrush(wx.Brush(wx.WHITE, wx.TRANSPARENT)) rect = wx.Rect(x, y, w, h) dc.DrawRectangle(rect) # remove the clipping region dc.DestroyClippingRegion() self._adjustScrollbars() def Redraw(self, dc=None): """Redraw the existing plot.""" if self.last_draw is not None: graphics, xAxis, yAxis = self.last_draw self._Draw(graphics, xAxis, yAxis, dc) def Clear(self): """Erase the window.""" self.last_PointLabel = None #reset pointLabel dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer) dc.Clear() self.last_draw = None def Zoom(self, Center, Ratio): """ Zoom on the plot Centers on the X, Y coords given in Center Zooms by the Ratio = (Xratio, Yratio) given """ self.last_PointLabel = None #reset maker x, y = Center if self.last_draw is not None: (graphics, xAxis, yAxis) = self.last_draw w = (xAxis[1] - xAxis[0]) * Ratio[0] h = (yAxis[1] - yAxis[0]) * Ratio[1] xAxis = (x - w / 2, x + w / 2) yAxis = (y - h / 2, y + h / 2) self._Draw(graphics, xAxis, yAxis) def GetClosestPointOnCurve(self, pntXY, label, pointScaled=True): """Returns list with [curveNumber, legend, index of closest point, pointXY, scaledXY, distance] list for the specified curve. Returns [] if no curves are being plotted. x, y in user coords if pointScaled == True based on screen coords if pointScaled == False based on user coords """ if self.last_draw is None: #no graph available return [] graphics, xAxis, yAxis = self.last_draw labels = [obj.getLegend() for obj in graphics] curveNum = labels.index(label) obj = graphics[curveNum] #check there are points in the curve if len(obj.points) == 0: return [] #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance] cn = [curveNum] + [obj.getLegend()] + obj.getClosestPoint(pntXY, pointScaled) return cn def GetClosestPoints(self, pntXY, pointScaled=True): """Returns list with [curveNumber, legend, index of closest point, pointXY, scaledXY, distance] list for each curve. Returns [] if no curves are being plotted. x, y in user coords if pointScaled == True based on screen coords if pointScaled == False based on user coords """ if self.last_draw is None: #no graph available return [] graphics, xAxis, yAxis = self.last_draw l = [] for curveNum, obj in enumerate(graphics): #check there are points in the curve if len(obj.points) == 0: continue #go to next obj #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance] cn = [curveNum] + [obj.getLegend()] + obj.getClosestPoint(pntXY, pointScaled) l.append(cn) return l def GetClosestPoint(self, pntXY, pointScaled=True): """Returns list with [curveNumber, legend, index of closest point, pointXY, scaledXY, distance] list for only the closest curve. Returns [] if no curves are being plotted. x, y in user coords if pointScaled == True based on screen coords if pointScaled == False based on user coords """ #closest points on screen based on screen scaling (pointScaled= True) #list [curveNumber, index, pointXY, scaledXY, distance] for each curve closestPts = self.GetClosestPoints(pntXY, pointScaled) if closestPts == []: return [] #no graph present #find one with least distance dists = [c[-1] for c in closestPts] mdist = min(dists) #Min dist i = dists.index(mdist) #index for min dist return closestPts[i] #this is the closest point on closest curve def UpdatePointLabel(self, mDataDict): """Updates the pointLabel point on screen with data contained in mDataDict. mDataDict will be passed to your function set by SetPointLabelFunc. It can contain anything you want to display on the screen at the scaledXY point you specify. This function can be called from parent window with onClick, onMotion events etc. """ if self.last_PointLabel is not None: #compare pointXY if _Numeric.sometrue(mDataDict["pointXY"] != self.last_PointLabel["pointXY"]): #closest changed self._drawPointLabel(self.last_PointLabel) #erase old self._drawPointLabel(mDataDict) #plot new else: #just plot new with no erase self._drawPointLabel(mDataDict) #plot new #save for next erase self.last_PointLabel = mDataDict # event handlers ********************************** def OnMotion(self, event): if self._zoomEnabled and event.LeftIsDown(): if self._hasDragged: self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old else: self._hasDragged = True self._zoomCorner2[0], self._zoomCorner2[1] = self._getXY(event) self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new elif self._dragEnabled and event.LeftIsDown(): coordinates = event.GetPosition() newpos, oldpos = map(_Numeric.array, map(self.PositionScreenToUser, [coordinates, self._screenCoordinates])) dist = newpos - oldpos self._screenCoordinates = coordinates if self.last_draw is not None: graphics, xAxis, yAxis = self.last_draw yAxis -= dist[1] xAxis -= dist[0] self._Draw(graphics, xAxis, yAxis) def OnMouseLeftDown(self, event): self._zoomCorner1[0], self._zoomCorner1[1] = self._getXY(event) self._screenCoordinates = _Numeric.array(event.GetPosition()) if self._dragEnabled: self.SetCursor(self.GrabHandCursor) self.canvas.CaptureMouse() def OnMouseLeftUp(self, event): if self._zoomEnabled: if self._hasDragged: self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old self._zoomCorner2[0], self._zoomCorner2[1] = self._getXY(event) self._hasDragged = False # reset flag minX, minY = _Numeric.minimum(self._zoomCorner1, self._zoomCorner2) maxX, maxY = _Numeric.maximum(self._zoomCorner1, self._zoomCorner2) self.last_PointLabel = None #reset pointLabel if self.last_draw is not None: self._Draw(self.last_draw[0], xAxis=(minX, maxX), yAxis=(minY, maxY), dc=None) if self._dragEnabled: self.SetCursor(self.HandCursor) if self.canvas.HasCapture(): self.canvas.ReleaseMouse() def OnMouseDoubleClick(self, event): if self._zoomEnabled: # Give a little time for the click to be totally finished # before (possibly) removing the scrollbars and trigering # size events, etc. wx.CallLater(250, self.Reset) def OnMouseRightDown(self, event): if self._zoomEnabled: X, Y = self._getXY(event) self.Zoom((X, Y), (self._zoomOutFactor, self._zoomOutFactor)) def OnPaint(self, event): # All that is needed here is to draw the buffer to screen if self.last_PointLabel is not None: self._drawPointLabel(self.last_PointLabel) #erase old self.last_PointLabel = None dc = wx.BufferedPaintDC(self.canvas, self._Buffer) def OnSize(self, event): # The Buffer init is done here, to make sure the buffer is always # the same size as the Window size = self.canvas.GetClientSize() size.width = max(1, size.width) size.height = max(1, size.height) # Make new offscreen bitmap: this bitmap will always have the # current drawing in it, so it can be used to save the image to # a file, or whatever. self._Buffer = wx.EmptyBitmap(size.width, size.height) if not self._isWindowCreated: return self._setSize() self.last_PointLabel = None #reset pointLabel if self.last_draw is None: self.Clear() else: self._clientSize = size graphics, xSpec, ySpec = self.last_draw self._Draw(graphics, xSpec, ySpec) def OnLeave(self, event): """Used to erase pointLabel when mouse outside window""" if self.last_PointLabel is not None: self._drawPointLabel(self.last_PointLabel) #erase old self.last_PointLabel = None def OnScroll(self, evt): if not self._adjustingSB: self._sb_ignore = True sbpos = evt.GetPosition() if evt.GetOrientation() == wx.VERTICAL: fullrange, pagesize = self.sb_vert.GetRange(), self.sb_vert.GetPageSize() sbpos = fullrange - pagesize - sbpos dist = sbpos * self._sb_yunit - (self._getYCurrentRange()[0] - self._sb_yfullrange[0]) self.ScrollUp(dist) if evt.GetOrientation() == wx.HORIZONTAL: dist = sbpos * self._sb_xunit - (self._getXCurrentRange()[0] - self._sb_xfullrange[0]) self.ScrollRight(dist) # Private Methods ************************************************** def drawCursor(self, time): pt = time * self._pointScale + self._pointShift try: self.time = pt[0] except: pass self.GetParent().cursorPanel.setTime(self.time) def _setSize(self, width=None, height=None): """DC width and height.""" if width is None: (self.width, self.height) = self.canvas.GetClientSize() else: self.width, self.height = width, height self.plotbox_size = 0.97 * _Numeric.array([self.width, self.height]) xo = 0.5 * (self.width - self.plotbox_size[0]) yo = self.height - 0.5 * (self.height - self.plotbox_size[1]) self.plotbox_origin = _Numeric.array([xo, yo]) def _drawPointLabel(self, mDataDict): """Draws and erases pointLabels""" width = self._Buffer.GetWidth() height = self._Buffer.GetHeight() tmp_Buffer = wx.EmptyBitmap(width, height) dcs = wx.MemoryDC() dcs.SelectObject(tmp_Buffer) dcs.Clear() self._pointLabelFunc(dcs, mDataDict) #custom user pointLabel function dc = wx.ClientDC(self.canvas) #this will erase if called twice dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV) #(NOT src) XOR dst def _drawLegend(self, dc, graphics, rhsW, topH, legendBoxWH, legendSymExt, legendTextExt): """Draws legend symbols and text""" # top right hand corner of graph box is ref corner trhc = self.plotbox_origin + (self.plotbox_size - [rhsW, topH]) * [.87, -1] #legendLHS= .091* legendBoxWH[0] # border space between legend sym and graph box legendLHS = legendBoxWH[0] - 80 # border space between legend sym and graph box lineHeight = max(legendSymExt[1], legendTextExt[1]) * 1.1 #1.1 used as space between lines dc.SetFont(self._getFont(self._fontSizeLegend)) for i in range(len(graphics)): o = graphics[i] s = i * lineHeight if isinstance(o, PolyMarker): pass # draw marker with legend #pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.) #o.draw(dc, coord= _Numeric.array([pnt])) elif isinstance(o, PolyLine): pass # draw line with legend #pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.) #pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.) #o.draw(dc, coord= _Numeric.array([pnt1, pnt2])) else: raise TypeError("object is neither PolyMarker or PolyLine instance.") # draw legend txt pnt = (trhc[0] + legendLHS + legendSymExt[0], trhc[1] + s + lineHeight / 2. - legendTextExt[1] / 2) dc.SetTextForeground(o.getColour()) dc.DrawText(o.getLegend(), pnt[0] + 3, pnt[1]) dc.SetFont(self._getFont(self._fontSizeAxis)) # reset def _titleLablesWH(self, dc, graphics): """Draws Title and labels and returns width and height for each""" # TextExtents for Title and Axis Labels dc.SetFont(self._getFont(self._fontSizeTitle)) if self._titleEnabled: title = graphics.getTitle() titleWH = dc.GetTextExtent(title) else: titleWH = (0, 0) dc.SetFont(self._getFont(self._fontSizeAxis)) xLabel, yLabel = graphics.getXLabel(), graphics.getYLabel() xLabelWH = dc.GetTextExtent(xLabel) yLabelWH = dc.GetTextExtent(yLabel) return titleWH, xLabelWH, yLabelWH def _legendWH(self, dc, graphics): """Returns the size in screen units for legend box""" if not self._legendEnabled: legendBoxWH = symExt = txtExt = (0, 0) else: # find max symbol size symExt = graphics.getSymExtent() # find max legend text extent dc.SetFont(self._getFont(self._fontSizeLegend)) txtList = graphics.getLegendNames() txtExt = dc.GetTextExtent(txtList[0]) for txt in graphics.getLegendNames()[1:]: txtExt = _Numeric.maximum(txtExt, dc.GetTextExtent(txt)) maxW = symExt[0] + txtExt[0] maxH = max(symExt[1], txtExt[1]) # padding .1 for lhs of legend box and space between lines maxW = maxW * 1.1 maxH = maxH * 1.1 * len(txtList) dc.SetFont(self._getFont(self._fontSizeAxis)) legendBoxWH = (maxW, maxH) return (legendBoxWH, symExt, txtExt) def _drawRubberBand(self, corner1, corner2): """Draws/erases rect box from corner1 to corner2""" ptx, pty, rectWidth, rectHeight = self._point2ClientCoord(corner1, corner2) # draw rectangle dc = wx.ClientDC(self.canvas) dc.SetPen(wx.Pen(wx.BLACK)) dc.SetBrush(wx.Brush(wx.WHITE, wx.TRANSPARENT)) dc.SetLogicalFunction(wx.INVERT) dc.DrawRectangle(ptx, pty, rectWidth, rectHeight) dc.SetLogicalFunction(wx.COPY) def _getFont(self, size): """Take font size and returns wx.Font""" return wx.Font(size, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) def _onePoint2ClientCoord(self, corner1): """Converts user point coords to client screen int coords x, y, width, height""" c1 = _Numeric.array(corner1) # convert to screen coords pt1 = c1 * self._pointScale + self._pointShift ptx, pty = pt1 return ptx, pty def _point2ClientCoord(self, corner1, corner2): """Converts user point coords to client screen int coords x, y, width, height""" c1 = _Numeric.array(corner1) c2 = _Numeric.array(corner2) # convert to screen coords pt1 = c1 * self._pointScale + self._pointShift pt2 = c2 * self._pointScale + self._pointShift # make height and width positive pul = _Numeric.minimum(pt1, pt2) # Upper left corner plr = _Numeric.maximum(pt1, pt2) # Lower right corner rectWidth, rectHeight = plr - pul ptx, pty = pul return ptx, pty, rectWidth, rectHeight def _axisInterval(self, spec, lower, upper): """Returns sensible axis range for given spec""" if spec == 'none' or spec == 'min': if lower == upper: return lower - 0.5, upper + 0.5 else: return lower, upper elif spec == 'auto': range = upper - lower if range == 0.: return lower - 0.5, upper + 0.5 log = _Numeric.log10(range) power = _Numeric.floor(log) fraction = log - power if fraction <= 0.05: power = power - 1 grid = 10. ** power lower = lower - lower % grid mod = upper % grid if mod != 0: upper = upper - mod + grid return lower, upper elif isinstance(spec, tuple): lower, upper = spec if lower <= upper: return lower, upper else: return upper, lower else: raise ValueError(str(spec) + ': illegal axis specification.') def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks): dc.SetPen(wx.Pen(self._gridColour, 1)) # set length of tick marks--long ones make grid if self._gridEnabled: x, y, width, height = self._point2ClientCoord(p1, p2) if self._gridEnabled == 'Horizontal': yTickLength = width / 2.0 + 1 xTickLength = 3 elif self._gridEnabled == 'Vertical': yTickLength = 3 xTickLength = height / 2.0 + 1 else: yTickLength = width / 2.0 + 1 xTickLength = height / 2.0 + 1 else: yTickLength = 3 xTickLength = 3 ### little speed improvment in drawing axes. - O.B. ### if self._xSpec is not 'none': lower, upper = p1[0], p2[0] text = 1 for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]: # miny, maxy and tick lengths a1 = scale * _Numeric.array([lower, y]) + shift a2 = scale * _Numeric.array([upper, y]) + shift dc.DrawLine(a1[0], a1[1], a2[0], a2[1]) # draws upper and lower axis line pts = [scale * _Numeric.array([x, y]) + shift for x, label in xticks] pts_line = [(pt[0], pt[1], pt[0], pt[1] + d) for pt in pts] dc.DrawLineList(pts_line) if text: labels = [label for x, label in xticks] dc.DrawTextList(labels, pts) # for x, label in xticks: # pt = scale*_Numeric.array([x, y])+shift # dc.DrawLine(pt[0], pt[1], pt[0], pt[1] + d) # draws tick mark d units # if text: # dc.DrawText(label, pt[0], pt[1]) text = 0 # axis values not drawn on top side if self._ySpec is not 'none': lower, upper = p1[1], p2[1] text = 1 h = dc.GetCharHeight() * 0.5 for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]: a1 = scale * _Numeric.array([x, lower]) + shift a2 = scale * _Numeric.array([x, upper]) + shift dc.DrawLine(a1[0], a1[1], a2[0], a2[1]) pts = [scale * _Numeric.array([x, y]) + shift for y, label in yticks] pts_line = [(pt[0], pt[1], pt[0] - d, pt[1]) for pt in pts] dc.DrawLineList(pts_line) if text: labels = [label for y, label in yticks] labels_coords = [(pt[0] - dc.GetTextExtent(labels[i])[0] - 2, pt[1] - h) for i, pt in enumerate(pts)] dc.DrawTextList(labels, labels_coords) # for y, label in yticks: # pt = scale*_Numeric.array([x, y])+shift # dc.DrawLine(pt[0], pt[1], pt[0]-d, pt[1]) # if text: # dc.DrawText(label, pt[0]-dc.GetTextExtent(label)[0]-2, pt[1]-0.5*h) text = 0 # axis values not drawn on right side def _xticks(self, *args): if self._logscale[0]: return self._logticks(*args) else: return self._ticks(*args) def _yticks(self, *args): if self._logscale[1]: return self._logticks(*args) else: return self._ticks(*args) def _logticks(self, lower, upper): #lower, upper = map(_Numeric.log10, [lower, upper]) #print('logticks', lower, upper) ticks = [] mag = _Numeric.power(10, _Numeric.floor(lower)) if upper - lower > 6: t = _Numeric.power(10, _Numeric.ceil(lower)) base = _Numeric.power(10, _Numeric.floor((upper - lower) / 6)) def inc(t): return t * base - t else: t = _Numeric.ceil(_Numeric.power(10, lower) / mag) * mag def inc(t): return 10 ** int(_Numeric.floor(_Numeric.log10(t) + 1e-16)) majortick = int(_Numeric.log10(mag)) while t <= pow(10, upper): if majortick != int(_Numeric.floor(_Numeric.log10(t) + 1e-16)): majortick = int(_Numeric.floor(_Numeric.log10(t) + 1e-16)) ticklabel = '1e%d' % majortick ############# mine ############ if not self.GetUseScientificNotation(): ticklabel = str(int(float(ticklabel))) else: if upper - lower < 2: minortick = int(t / pow(10, majortick) + .5) ticklabel = '%de%d' % (minortick, majortick) ############# mine ############ if not self.GetUseScientificNotation(): ticklabel = str(int(float(ticklabel))) else: ticklabel = '' ticks.append((_Numeric.log10(t), ticklabel)) t += inc(t) if len(ticks) == 0: ticks = [(0, '')] return ticks def _ticks(self, lower, upper): ideal = (upper - lower) / 7. log = _Numeric.log10(ideal) power = _Numeric.floor(log) fraction = log - power factor = 1. error = fraction for f, lf in self._multiples: e = _Numeric.fabs(fraction - lf) if e < error: error = e factor = f grid = factor * 10. ** power if self._useScientificNotation and (power > 4 or power < -4): format = '%+7.1e' elif power >= 0: digits = max(1, int(power)) format = '%' + repr(digits) + '.0f' else: digits = -int(power) format = '%' + repr(digits + 2) + '.' + repr(digits) + 'f' ticks = [] t = -grid * _Numeric.floor(-lower / grid) while t <= upper: ticks.append((t, format % (t,))) t = t + grid return ticks _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))] def _adjustScrollbars(self): if self._sb_ignore: self._sb_ignore = False return if not self.GetShowScrollbars(): return self._adjustingSB = True needScrollbars = False # horizontal scrollbar r_current = self._getXCurrentRange() r_max = list(self._getXMaxRange()) sbfullrange = float(self.sb_hor.GetRange()) r_max[0] = min(r_max[0], r_current[0]) r_max[1] = max(r_max[1], r_current[1]) self._sb_xfullrange = r_max unit = (r_max[1] - r_max[0]) / float(self.sb_hor.GetRange()) pos = int((r_current[0] - r_max[0]) / unit) if pos >= 0: pagesize = int((r_current[1] - r_current[0]) / unit) self.sb_hor.SetScrollbar(pos, pagesize, sbfullrange, pagesize) self._sb_xunit = unit needScrollbars = needScrollbars or (pagesize != sbfullrange) else: self.sb_hor.SetScrollbar(0, 1000, 1000, 1000) # vertical scrollbar r_current = self._getYCurrentRange() r_max = list(self._getYMaxRange()) sbfullrange = float(self.sb_vert.GetRange()) r_max[0] = min(r_max[0], r_current[0]) r_max[1] = max(r_max[1], r_current[1]) self._sb_yfullrange = r_max unit = (r_max[1] - r_max[0]) / sbfullrange pos = int((r_current[0] - r_max[0]) / unit) if pos >= 0: pagesize = int((r_current[1] - r_current[0]) / unit) pos = (sbfullrange - 1 - pos - pagesize) self.sb_vert.SetScrollbar(pos, pagesize, sbfullrange, pagesize) self._sb_yunit = unit needScrollbars = needScrollbars or (pagesize != sbfullrange) else: self.sb_vert.SetScrollbar(0, 1000, 1000, 1000) self.SetShowScrollbars(needScrollbars) self._adjustingSB = False cecilia5-5.4.1/Resources/Cecilia_splash.png000066400000000000000000000551121372272363700206020ustar00rootroot00000000000000PNG  IHDRŐgbKGD pHYs  ~tIME  D IDATxit\u;g-^W|W biAA ,syW R5`_NמR +eү_  ܥhΪW#&Pk#Fzqr>)AtfF&I%t֭GkÀG)ٶM{wjƿ/ -p%|  @}駷Piʧ>Go  8ϞW_j,kW&?ݽAMX^:ziT"_ըנAGZ F?}WA_W(CR R,[K Btv/k @)ŲA  !=VVWt"]b!AAhCzizutA ;Q #( Bw P(  퇦iM~^ bAA  AAD  @A!&hGl"A: P,)EibY&: un? p8bZAuMrzeq #C>gP(Lell۾v8JݨO " "@A j&=wɉdI4R6s۲lm [)@Gi:Jw@hvškVXV(- ,celӤX.c*%ЯhӉ?$IW2z茢r nrD| FqeA!o`ZiaY6(u8rWp:q86kʥ"|R>Y4(s  9qή^WdʵAD,M0gH%%Lem @o0kC)p\8].E*) 3IJB\1I2&v,C{vD\QJRAD)t N1z4Sq"eƲm4/'؉m͙p=˲) r%#E!#k\%Hgֱrz S hqʥ"F93|(XITi())JY\>1x~6>}W# g'1z F6M\lN?p@E8].e(Sq ]v~ツ;:` ".9qbeBsw-:x<^q3~BiDwz"!,٩KdifiV^Ϧ!$K "n7F6É#9she+ (Yd2IN; ]{{@ADzʥ"sW+0^q@&"Dbzvpoep:rXAŲ,^ȱC26JP=: :H @  MOc$Ǹr2_wXBMv\U8gzj~)1 K%by`߽tvߦ躃~]k)Z؅KbAD,rGOge6K`H߽ ryogbۖG%Ò\8'$W H/[Gr x<"&A]Ego; Jq eKڻ 20 8~h/Ol)kd_%:zP8uO n,N8̉#): "R'2p?w!1 "4Ns`9gpPT>YG"=FC{w. $>Y#MGt9J ˢkDzpx3Y N`MI4W_턻W\>.]䝷^\*QAX%~#F{]w'*v.'՘ # ݉eYbAD,gNcM?:OSX4^jGxEɉ?\>s`G|¢Gp{ڿtjZ"ۅY.s`[$SIBn;F 2q2ᄑr$FAƲLx񋣘N0:k) w Ak >vۖ|AZ`j4zcC,w%۶IMǹzl|>G-+.4]wtyB|AN璶׵#~cGo Ap+0 ˺J&tq+Wc r,R*EQ9uU31=KU&kd4 Å! ꢧg9=%aPb6A&3š}_NTADoUlP%re|sJu)n/B"j5esAS 91.Xc^P ² \lO`j,Õ>yl Rϩ8w%]!N0r8^ۋDu|x ;zkwې1mm &W22zg(V,_C_*\nO. _G?Fbճe"A{Lzt2#Ћ?б?|`)Na:'@i:(EH6T$I&7NQ׹Q0-dZgWQHe9rb{^4MNr[PBQ )ҩi_'q8]2 f9u0+XJ'ٷOůp;\V>L%'8-v.L$Imۇ42FS#G8{>#Mudj<񋌞;Úe7QxhO>)1 38~h7 kͤLS;d&A$ԅPYRngdZTkm6ضCo׃'K1=cCX.uCdžb "]L&&'&Fy| a6s?OޝU}^\X"n!$8|`38xxeT.2Gf+5L>l;{^G?Ԝj+V(ll[U*l[;z &''83|<&]I^~%bc<'?K45GK%^>~\$;d#ۚEC7eX #g0|a:BtFzC #E:3M2 I`Y^w(W7:j*l\zw5G?9}06>?Q1{Z~׸o'_x` bc}[.&NpC|J;v+1.7+ͬ7:#q3}ꚡ~hGYT. tvF]} ;*Qzg&#N0r8n/T (܃j4@MZ|A|;0 #Ñy'ksk/֛_'kbT d˦Bpg8Νl#ڸ(;vK\"W4*_ hpr&66}׷^^tu6nfr,NSD;z0r\-0Wiri*T AROr0m~p$*#"0m"y<6db_{ k7 mss;ܶ˲8rN 19}Y-uE D--tzssa:zRYǾHQhׄM5RFHgO>+H>C|`GeYd`6$ aX+9]?9б!>1\uF |Oz#''CǷ>O<%").`с. O^A4*5+jQ @!MuX(v{ټ1֬t:Ҕ25n7ΞvuY 夯)6>,vƭKoΜGNt5l0$o~͆>yݮ};:v(DT!pvh]%x~kR s! + dX`qu6ɥ3_k%ljá;{l/.~?ȇ8O̅c]^܊ Au_)Uh}>?YG2r8?A}-}]v+ko({ eD#] օr93<>0S ܏wS%5(jvss[k볙FNa6@\.[/se"|*4^]f1i 9Te¡;o>_t͛>m=jjF@3"UĨ=PJlZ>AӢ3ܜ#1tlgذ9Iz|_ Y }xǷ<^Kh'?~kx=^r\=""H9ul.}ڬmY.[?&7 u߼_qIMzhdzm2+^b-{=j$(muar$ΜHϼOع;5V=WP^7߰^]3Ͻ=GN&( ?;ix:{۪LVLO1?i3~:6tz,_|tޅ sZ0Cvqys3"fεF9!eqȆ5{bK:[qr_׮܃ԝ`ڴ^5Qa+._$J`{Y#=kVIl5!И@i31 wͥٺw18x܌Cwbە3KmX#(RqeZ,v&9n+7F]vNhuG[ֶm dʅaMCӵz_?P}YXA]&PI`lpvviR* p:=a HD#]uLJ]v69w24>?k aGEX5!~3rܖ_:_2J%ع3X%"[lyyN_˂߾ jY_#}kw k_O>mݿ*6gh=J&{vZwCo7eca,[6rٲq,Ķ,lji@9ZgdZ,3c1nuF}>{jdx^Gm}uEfs^}{"ܪ$.6F)M\Ιf7-ήw~$N4%gM㯵­4wW`lnI \O&*Յ@I(pl3GeZDꗿF4e_xbnBG՗~|;ϰkNGNkN߬;C+{yכfmá<պη+BkF=Q{|ɖMtBΣ?up89Ir`8o6yZ IDATN*3TjeNmּjkZS߼3GO,#oi[|!Μ?^]UcBضahO %`s-`'8].'yevF IroϽ}Jִ]OLrq{};oP.IeR@*Z %t[f9AXT@X`j*ev?15++5oLoi@Ì̈C>O-w==A Sɉ *En_1AbjrQI\H‰#lf%5Zwsf`B{ڠBb!נ)m@CS ] Bm}ض#ajHwnvY>X^_@ٲqx-'fɆc?`XH0%n p{2|pz"},~cm5d4*#"䇯񔜋~`e;<];]7UذLmcc\KK+4UekOL2<2_2⍾B2߆@FX=k%V]x[ˢo4| S)to53 >H&jﯚ6oZg6U@21o޽?g1BP RuAr?y_q:\%0QCl<t8Y3یضu5aA<1?}klXQε-#g_ Ny+3+SGyg.6PoC?۹O>kncclXs/SGٵ'[6ns/~#pw?V}n #'.xy1w.5Q02:o#:7r,*x53G:L^mN>WLq@G0Cs\5RB!R ۋ~]K +#[pyg-ҩif|7y|e>Kk ;Ƿ>ضu5#HWY=)o^>zcĶ}y{tEǸa*|]뽟{֯mql|D-(v|ՍD#]m#?is5GME #,ܞWc 35fg UM,[໻WczOU]"QLCtbo @X̷ܾ if2*50tl״ݓO[DLůR.[(I;w/D!UhgB_4ptI\ilZ>њ|RRx<)4e_P+El<`(W'7䓟bI5>oJr=jm~_'5Ŋ3O˟?s/~ k6#%a3Zlwa%8,IJ+-5dY3Xg79Ǯ~4M["]FMztE5%S6ol[}۶HNOT塍[*aVSVб!G|_J]wmOy,7$"F[n'#g+/'O'>˓O?/1\AB-ΦX*r?]G3eg4(AkmkvXֹ6l] ?&P% 1\%&6GL즘 \_gwr8+W~7d0>)|n bl766{_a(F>@{=]wt܂~74BȮHt^-ޯz#ߐE ll%K(+!(M??)|rLw ]8Rwb3B۶J;i۲x};L[*`>k7Y7V$9W~=/h뚎zj;ο'eQ@X4m-Nƿ1[_kk9b9Ow ]3(6O|˽$Z";l"/MkNڸE $ˢeJEL -I*=igfmTא?S0O =( Run`.7Q*ednz6}jdnlly>/;5hl.i(k iN";е%\5_GKX躎Ʋ,SF(ذfڿCtElXso=^Y̲R۾T,+oZԵ翍| e- в![@1J@SF(ٲi˜zAkfB b!ß7Ff9J+k2`ET_RMdt=@P[L&hXii:p- %FPH" EnE,J$9op37:ͷNՐR ,391$ EDq=mov.AZ^Xm)h T:1SPrIM=\uSI^#iT[r>=5z.AŢycӬϵ  k]K- jHz,Ye:x Z>\8nO^}ɫc)L"{E\,577ۏMkK- ޮNmfLa.xrda6FV>S" Ls䭍`pjo %&n>gs2Őj@c׿6E"jSF'AC b, \(ռ*"<B~|&NS2yzt9C,p- f2δz֜93ףܢ齲j}^._XrCS:5́Co,:(Np!V}"]i (Zs`5io̾Ro*`?˼ֿvxȔ3ugYLrmsm;nAn|f6nEȲ1lN[6o#Nyrꥶ9Z+v4 tjh{bNh6fmb Wdjr)ZXAncەL]0ؔ 樄SeH+tttr(avwoGP'Kdي5D:GrDЦ@)Yʵ:qeR޶;o.;_Ps" _ȁmfh**W2PN +ihfl9[|ɡ鸕i+ $# E]B([aZs `zL=#AmᶹuPvevn?Y,p {;phYm%[P66Ag;_7?MvpDF&A+v4K-'Iԝ=n?$0wl}P 299No@[_|mʺ3zgxun2`CT I#:޶Y*94:Ah+o/6-~_E"GEP8WJX04ٶPoͬ]l:I"1T*Y&ꤻ{9PG[gۖmqn::Ef t %J.w:vL';mcjDyu=P®(  0`ympp m@WPȷf"՛z3ͫv-+ZSNP9 \KEi´G ,QQJQ.[`}yJ;HȶyrY )'.tM7R-kxܾjL P 4l\s,e#'(sLǘNƙNN2L"9emyޥ4v# v/Ci?H:ġ;)`תly`,PQ$Rɉ%q1:B) ˚MHN29u=@!4zVȈ$B{ +qhD@˴[p(=>: hP go77Ty9/dbtvT4UCOjx۶) t]1rH )kAF۪FF1P@m5 fb2L/G%4K5\޶;oȠ)  DGgH uSL:=nɀB6k}T@U>O᳇Bp8~MՖR.v]L~Ah? S̵M$¡;Qu~U@;̤95rl.S QsXe5/<2 4#څF:޲TJ$Nuy{8f- 0Dbd{ef}Oc⟦5%ZEwO{u˥&ѕNW2F , rz"}=hXM}fZ&aɀ(eNʥR[\|WGrd:QfI-1CW^wD ,-0\nv1GPhiCw#n:z+!/$ye/tc?`U߸/aEd$ai ?@gM0S-mH#[~L6Ŷ戀ݰpm9C7 4EHr6v-mÇsL&.ϙzGD F8Fғ8tY'# KKZ ɖ7fgtg`a S`Cl{8tj%픚bρ+$j_-_\nBH|,ӢF GF!AXv,f/?A"ˈ{C-̊4TfJgә)./rfRt5RvMߍcIDAT+͒S'3(Hgd Tp5p`e:ؖոа`7 Z0'R,ػy-mɩ͎a<7CZ޿}@ Cgʵmű "n=%#Rl!<^m|?P(YI4o'\INF0{vjTe #޻l|N>q-f9b `ېK_Fl~4MH-V`,MOW6  Fh#+XvmRH0:K44 foԀm7+-k3UN'}-~iei)$YѪ 9O-}hJl <`5'fB+Vcz=r=_T* 4.kzT`V#hfTvYb*9O?{o6 o&̬7;B- ݝZoZdc8:kG: `6m`Rmh6]}|䃿@$š9 Y ͂f' 6@)LT*nsacېIOƛ//P*)Z%ĬY|L֮y6*f C: u (#d6Q>O3tm |?lU9Jx}#!6z᳇0r6ޝƕޭ^,"EJJԮԭ Sy_𳟍$/y`cI&+zt[[j픸dYۭ.܊5ӭ`A,RιCeoomnQ:;~@+dpDJuersw);e*2XP糯Nx;Q44M{[NV@ӡPWht>::{SyKZ&jvbim Mk:M% }.ֻ'G6>'i c'G!I$k8k' _4x>RsHiK=m [/Զ:'CRPo֨5(a4E)+k!aJ Zw'ZnN"ͯG o& iFjc][>m7/_v#t{IyQ !$<i2}- kYJ"p+3}mLӒ!$(ts#p8x,Jd2ť(B /Z ! ')IkZq\P"𲜚>ϱtI%Jm6){x4FYla-'( %\'G<HFTB މѓ̜HآQzL,w\R+.n.@O'?`| cfCNN))-əLL!$73} ?rag!Scdb3u]j#vo](=VHK}=Ǚ>aHa+ qݟrbjR|-rQZ[|T=)޾9xJ!` \|5}*GZ\|ȿ{NLr)BA'),=rRaMn+ʅ9LCqbxBC<a>ar m "vHgdb`Y!)BAŹO1M7sIwc貨K:M~E ^_!.hjik@7䶮ו뺔rhL$BH8L,+ĩ7D򳿣X\%5h huh)'' =d}!+<G!щIB (HdׯQIdDrIV*P2To126%[ !$f.tauuMm}$3=h,?)P/.aa HqxIRho}A7 tŧhK$3#逩ks2pq8&DR#KK/`J3\?WqOVK"&0d~{Y%4֖w[Bw:KO?{S+.Ҭ扦'dm>8T KC8d{tsjk !i.._={op/ɭR^EEeIfNy1ihtvuqr džF1eK_! Œ/>5ܡVZ!WYN!4e%>N>K* 204EɴH!$8dF߳QǭI!Or]evrZ1ʫ`Y=.]=}R !hݽ7d}=zj!Fx<)^TԋnHwv11u0LyI !$;5͑ǸݥX(|K58W B=GJ)*Njaլ`Z&m?1Ro!$Sm}N>_wnP9oRDRGS>:" $i=DJ$^X<ŷ2vj7`-*"`Y!<=&N1KkX"޸S!WO#΅>`r"n7(4KTXD,|hߣ攩;9zC2H%;bh$v$*B S4gj"yn (dKTr&xHێֵR^Q+hV N<) bB C؎02qs:sw)4J+K+hF;A4N(ztrJA^ѬPn][&TG342A[{'.'Btp6 qurecjEtƴIhPm0JSmonK'tt14rޣd/aM'9~bzң9f# AmJ5Kð18XH$v&p]F͡Y+nTne#1;964c'6>!а(C'^x9ֳ+8M_kPR@lP3Ųv_>i4knoV :`:۶tt08H! 0LҙҙF'NPVX]^$ZrHQu}Bj TR fhk(MG4@G7f6)|?@) QJk:i ]P!j#GwQl; ! ~H4ƱQ n[^# SZqU+4u||GFGxT{Fl.7t ] i:FGƉDcē);ɴwbGh. %ċ# 70fSVPp&y{J)t]0L4}cV mH9g BH ΄Ig:B!v!B!@!B!@!BqR s:B!!\ B!aT7co jcZ@Q.BB!!Tȯ`{ܿʒTH!8ֳ+[lnY B!ġغ `iaN*$B2FVPU@)hk<K*%B"swik[ u[OPJo>&|BqH|ǻnR Rba7j !?)[GU? O?ZujB!V*_?99pok#?@iZyR=!\s4 6e?hv60]?shiՊA~m~G`v p SxHO/Bxε~k?~4;۾{w?I Hll HB!^~o~_u?\.'RIENDB`cecilia5-5.4.1/Resources/Control.py000066400000000000000000002333431372272363700171670ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx, os, math, copy import wx.lib.scrolledpanel as scrolled import Resources.CeciliaLib as CeciliaLib from .constants import * from .Widgets import * from .TogglePopup import SamplerPopup, SamplerToggle from .Plugins import * class CECControl(scrolled.ScrolledPanel): def __init__(self, parent, id=-1, size=(-1, -1), style=wx.BORDER_SIMPLE): scrolled.ScrolledPanel.__init__(self, parent, id, size=size, style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.outputFilename = '' self.cfileinList = [] self.peak = '' self.time = self.nonZeroTime = 0 self.charNumForLabel = 34 self.bounce_dlg = None self.tmpTotalTime = CeciliaLib.getVar("totalTime") self.sizerMain = wx.FlexGridSizer(0, 1, 0, 0) self.sizerMain.Add(Separator(self, (230, 1), colour=TITLE_BACK_COLOUR), 1, wx.EXPAND) ##### Transport Panel ##### controlPanel = wx.Panel(self, -1, style=wx.BORDER_NONE) controlPanel.SetBackgroundColour(TITLE_BACK_COLOUR) controlSizer = wx.FlexGridSizer(1, 4, 0, 0) self.transportButtons = Transport(controlPanel, outPlayFunction=self.onPlayStop, outRecordFunction=self.onRec, backgroundColour=TITLE_BACK_COLOUR, borderColour=WIDGET_BORDER_COLOUR) self.clocker = Clocker(controlPanel, backgroundColour=TITLE_BACK_COLOUR, borderColour=WIDGET_BORDER_COLOUR) controlSizer.Add(self.transportButtons, 0, wx.ALIGN_LEFT | wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) fakePanel = wx.Panel(controlPanel, -1, size=(35, self.GetSize()[1])) fakePanel.SetBackgroundColour(TITLE_BACK_COLOUR) controlSizer.Add(fakePanel) controlSizer.Add(self.clocker, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) ffakePanel = wx.Panel(controlPanel, -1, size=(5, self.GetSize()[1])) ffakePanel.SetBackgroundColour(TITLE_BACK_COLOUR) controlSizer.Add(ffakePanel) #controlSizer.AddGrowableCol(1) controlPanel.SetSizer(controlSizer) self.sizerMain.Add(controlPanel, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 0) self.sizerMain.Add(Separator(self, (230, 1), colour=TITLE_BACK_COLOUR), 1, wx.EXPAND) self.sizerMain.Add(Separator(self, (230, 2), colour=BORDER_COLOUR), 1, wx.EXPAND) self.sizerMain.Add(5, 1, 0) self.tabs = TabsPanel(self, outFunction=self.onTogglePanels) self.sizerMain.Add(self.tabs, 1, wx.ALIGN_CENTER | wx.ALL, 0) ##### Input Panel ##### self.inOutSeparators = [] isEmpty = self.createInputPanel() self.sizerMain.Add(self.inputPanel, 1, wx.EXPAND | wx.ALL, 0) if not isEmpty: sep = Separator(self, (230, 2), colour=BACKGROUND_COLOUR) self.sizerMain.Add(sep, 1, wx.EXPAND) self.inOutSeparators.append(sep) sep = Separator(self, (230, 2), colour=BORDER_COLOUR) self.sizerMain.Add(sep, 1, wx.EXPAND) self.inOutSeparators.append(sep) sep = Separator(self, (230, 1), colour=BACKGROUND_COLOUR) self.sizerMain.Add(sep, 1, wx.EXPAND) self.inOutSeparators.append(sep) ###### Output Panel ##### self.createOutputPanel() self.sizerMain.Add(self.outputPanel, 1, wx.EXPAND | wx.ALL, 0) sep = Separator(self, (230, 2), colour=BACKGROUND_COLOUR) self.sizerMain.Add(sep, 1, wx.EXPAND) self.inOutSeparators.append(sep) sep = Separator(self, (230, 2), colour=BORDER_COLOUR) self.sizerMain.Add(sep, 1, wx.EXPAND) self.inOutSeparators.append(sep) sep = Separator(self, (230, 1), colour=BACKGROUND_COLOUR) self.sizerMain.Add(sep, 1, wx.EXPAND) self.inOutSeparators.append(sep) ### Plugins panel ### self.createPluginPanel() self.sizerMain.Add(self.pluginsPanel, 0, wx.ALL, 0) self.sizerMain.Show(self.pluginsPanel, False) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) controlPanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.inputPanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.outputPanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.peakLabel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.durationSlider.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.gainSlider.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.vuMeter.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.pluginsPanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.SetSizer(self.sizerMain) self.SetAutoLayout(1) self.SetupScrolling(scroll_x=False) wx.CallAfter(self.updateOutputFormat) def OnLooseFocus(self, event): win = wx.FindWindowAtPointer() if win[0] is not None: win = win[0].GetTopLevelParent() if win not in [CeciliaLib.getVar("mainFrame"), CeciliaLib.getVar("interface"), CeciliaLib.getVar("spectrumFrame")]: win.Raise() event.Skip() def onTogglePanels(self, state): if state == 0: self.sizerMain.Show(self.pluginsPanel, False, True) self.sizerMain.Show(self.inputPanel, True, True) self.sizerMain.Show(self.outputPanel, True, True) [self.sizerMain.Show(sep, True, True) for sep in self.inOutSeparators] else: self.sizerMain.Show(self.pluginsPanel, True, True) self.sizerMain.Show(self.inputPanel, False, True) self.sizerMain.Show(self.outputPanel, False, True) [self.sizerMain.Show(sep, False, True) for sep in self.inOutSeparators] self.sizerMain.Layout() def createGrapherLines(self, plugin): knobs = [plugin.knob1, plugin.knob2, plugin.knob3] grapher = CeciliaLib.getVar("grapher") for j, knob in enumerate(knobs): func = '0 %f 1 %f' % (knob.GetValue(), knob.GetValue()) func = [float(v.replace('"', '')) for v in func.split()] func = [[func[i * 2] * CeciliaLib.getVar("totalTime"), func[i * 2 + 1]] for i in range(len(func) // 2)] mini = knob.getRange()[0] maxi = knob.getRange()[1] colour = CeciliaLib.chooseColourFromName('orange%d' % (j + 1)) label = knob.getLongLabel() log = knob.getLog() name = knob.getName() grapher.plotter.createLine(func, (mini, maxi), colour, label, log, name, 8192, knob, '') grapher.plotter.getData()[-1].setShow(0) choice = grapher.toolbar.getPopupChoice() choice.extend([knob.getLongLabel() for knob in knobs]) grapher.toolbar.setPopupChoice(choice) grapher.plotter.draw() def removeGrapherLines(self, plugin): knobs = [plugin.knob1, plugin.knob2, plugin.knob3] tmp = [knob.getLongLabel() for knob in knobs] names = [knob.getName() for knob in knobs] grapher = CeciliaLib.getVar("grapher") choice = grapher.toolbar.getPopupChoice() for label in tmp: if label in choice: choice.remove(label) grapher.toolbar.setPopupChoice(choice) grapher.plotter.removeLines(names) def movePlugin(self, vpos, dir): i1 = vpos i2 = vpos + dir grapher = CeciliaLib.getVar("grapher") choice = grapher.toolbar.getPopupChoice() for i in [i1, i2]: if self.plugins[i].pluginName != 'None': for label in self.plugins[i].getKnobLongLabels(): choice.remove(label) tmp = copy.deepcopy(self.pluginsParams[i1]) self.pluginsParams[i1] = copy.deepcopy(self.pluginsParams[i2]) self.pluginsParams[i2] = tmp self.plugins[i1], self.plugins[i2] = self.plugins[i2], self.plugins[i1] self.plugins[i1].vpos = i1 self.plugins[i2].vpos = i2 for i in [i1, i2]: self.plugins[i].setKnobLabels() self.plugins[i].checkArrows() graphData = CeciliaLib.getVar("grapher").getPlotter().getData() if self.plugins[i1].pluginName == 'None': CeciliaLib.setPlugins(None, i1) else: oldKnobNames = self.plugins[i1].getKnobNames() self.plugins[i1].setKnobNames() for i, old in enumerate(oldKnobNames): for line in graphData: if line.name == old: line.name = self.plugins[i1].getKnobNames()[i] break CeciliaLib.setPlugins(self.plugins[i1], i1) choice.extend(self.plugins[i1].getKnobLongLabels()) if self.plugins[i2].pluginName == 'None': CeciliaLib.setPlugins(None, i2) else: oldKnobNames = self.plugins[i2].getKnobNames() self.plugins[i2].setKnobNames() for i, old in enumerate(oldKnobNames): for line in graphData: if line.name == old: line.name = self.plugins[i2].getKnobNames()[i] break CeciliaLib.setPlugins(self.plugins[i2], i2) choice.extend(self.plugins[i2].getKnobLongLabels()) grapher.toolbar.setPopupChoice(choice) p1pos = self.bagSizer.GetItemPosition(self.plugins[i1]) p2pos = self.bagSizer.GetItemPosition(self.plugins[i2]) self.bagSizer.SetItemPosition(self.plugins[i1], (8, 0)) self.bagSizer.SetItemPosition(self.plugins[i2], p1pos) self.bagSizer.SetItemPosition(self.plugins[i1], p2pos) self.plugins[i1].Refresh() self.plugins[i2].Refresh() self.bagSizer.Layout() if CeciliaLib.getVar("audioServer").isAudioServerRunning(): CeciliaLib.getVar("audioServer").movePlugin(vpos, dir) def replacePlugin(self, order, new): ind = PLUGINS_CHOICE.index(self.plugins[order].getName()) self.pluginsParams[order][ind] = self.plugins[order].getParams() if ind != 0: self.removeGrapherLines(self.plugins[order]) plugin = self.pluginsDict[new](self.pluginsPanel, self.replacePlugin, order) if new != 'None': CeciliaLib.setPlugins(plugin, order) self.createGrapherLines(plugin) ind = PLUGINS_CHOICE.index(plugin.getName()) plugin.setParams(self.pluginsParams[order][ind]) else: CeciliaLib.setPlugins(None, order) plugin.setParams([0, 0, 0, 0]) self.plugins[order].cleanup() itempos = self.bagSizer.GetItemPosition(self.plugins[order]) item = self.bagSizer.FindItem(self.plugins[order]) if item.IsWindow(): item.GetWindow().Destroy() self.bagSizer.Add(plugin, itempos) self.plugins[order] = plugin self.bagSizer.Layout() wx.CallAfter(self.SetSize, self.GetSize()) if CeciliaLib.getVar("audioServer").isAudioServerRunning(): CeciliaLib.getVar("audioServer").setPlugin(order) def setPlugins(self, pluginsDict): for key in pluginsDict.keys(): self.replacePlugin(int(key), pluginsDict[key][0]) self.plugins[int(key)].setParams(pluginsDict[key][1]) self.plugins[int(key)].setStates(pluginsDict[key][2]) for i in range(NUM_OF_PLUGINS): if str(i) not in pluginsDict.keys(): self.replacePlugin(i, "None") def updateTime(self, time): self.setTime(time) self.GetParent().grapher.plotter.drawCursor(time) def updateAmps(self, amps): self.vuMeter.setAmplitude(amps) def createInputPanel(self): isEmpty = True self.inputPanel = wx.Panel(self, -1, style=wx.BORDER_NONE) inputSizer = wx.FlexGridSizer(0, 1, 0, 0) self.cfileinList = [] samplersList = [] widgets = CeciliaLib.getVar("interfaceWidgets") for w in range(len(widgets)): if widgets[w]['type'] == 'cfilein': cFileIn = Cfilein(self.inputPanel, label=widgets[w].get('label', ''), name=widgets[w]['name']) self.cfileinList.append(cFileIn) elif widgets[w]['type'] == 'csampler': cSampler = CSampler(self.inputPanel, label=widgets[w].get('label', ''), name=widgets[w]['name']) self.cfileinList.append(cSampler) samplersList.append(cSampler) CeciliaLib.setVar("userSamplers", samplersList) if self.cfileinList != []: isEmpty = False # Section title inputTextPanel = wx.Panel(self.inputPanel, -1, style=wx.BORDER_NONE) inputTextPanel.SetBackgroundColour(TITLE_BACK_COLOUR) inputTextSizer = wx.FlexGridSizer(1, 1, 0, 0) inputText = wx.StaticText(inputTextPanel, -1, 'INPUT') inputText.SetFont(wx.Font(SECTION_TITLE_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) inputText.SetBackgroundColour(TITLE_BACK_COLOUR) inputText.SetForegroundColour(SECTION_TITLE_COLOUR) inputTextSizer.Add(inputText, 0, wx.ALIGN_RIGHT | wx.ALL, 3) inputTextSizer.AddGrowableCol(0) inputTextPanel.SetSizer(inputTextSizer) inputSizer.Add(inputTextPanel, 1, wx.EXPAND, 0) for i in range(len(self.cfileinList)): inputSizer.Add(self.cfileinList[i], 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, -1) if i != len(self.cfileinList) - 1: inputSizer.Add(Separator(self.inputPanel, size=(230, 1)), 1, wx.EXPAND) inputSizer.AddGrowableCol(0) self.inputPanel.SetSizer(inputSizer) return isEmpty def createOutputPanel(self): self.outputPanel = wx.Panel(self, -1, style=wx.BORDER_NONE) self.outputPanel.SetBackgroundColour(BACKGROUND_COLOUR) outputSizer = wx.FlexGridSizer(0, 1, 0, 0) outputTextPanel = wx.Panel(self.outputPanel, -1, style=wx.BORDER_NONE) outputTextPanel.SetBackgroundColour(TITLE_BACK_COLOUR) outputTextSizer = wx.FlexGridSizer(1, 1, 0, 0) outputText = wx.StaticText(outputTextPanel, -1, 'OUTPUT') outputText.SetFont(wx.Font(SECTION_TITLE_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) outputText.SetBackgroundColour(TITLE_BACK_COLOUR) outputText.SetForegroundColour(SECTION_TITLE_COLOUR) outputTextSizer.Add(outputText, 0, wx.ALIGN_RIGHT | wx.ALL, 3) outputTextSizer.AddGrowableCol(0) outputTextPanel.SetSizer(outputTextSizer) outputSizer.Add(outputTextPanel, 1, wx.EXPAND, 0) outputSizer.AddGrowableCol(0) outputSizer.Add(5, 7, 0) outLine1 = wx.BoxSizer(wx.HORIZONTAL) # File Name Label self.filenameLabel = OutputLabel(self.outputPanel, label='', size=(130, 20), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onSelectOutputFilename) CeciliaLib.setToolTip(self.filenameLabel, TT_OUTPUT) self.filenameLabel.setItalicLabel('File name') outLine1.Add(self.filenameLabel, 0, wx.LEFT | wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) outLine1.Add(28, 1, 0) outToolbox = ToolBox(self.outputPanel, tools=['play', 'edit', 'recycle'], outFunction=[self.listenSoundfile, self.editSoundfile, self.onReuseOutputFile]) CeciliaLib.setToolTip(outToolbox, TT_OUTPUT_TOOLS) outLine1.Add(outToolbox, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 2) outputSizer.Add(outLine1, 1, wx.EXPAND | wx.LEFT | wx.BOTTOM, 7) # Duration Static Text durationText = wx.StaticText(self.outputPanel, -1, 'Duration (sec) :') durationText.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) durationText.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) outputSizer.Add(durationText, 0, wx.ALIGN_LEFT | wx.LEFT, 9) # Duration Slider outputSizer.Add(3, 1, 0) self.durationSlider = ControlSlider(self.outputPanel, 0.01, 3600, CeciliaLib.getVar("defaultTotalTime"), size=(220, 15), log=True, backColour=BACKGROUND_COLOUR, outFunction=self.setTotalTime) self.durationSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.durationSlider, TT_DUR_SLIDER) outputSizer.Add(self.durationSlider, 0, wx.ALIGN_LEFT | wx.LEFT | wx.BOTTOM, 7) # Gain Static Text gainText = wx.StaticText(self.outputPanel, -1, 'Gain (dB) :') gainText.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) gainText.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) outputSizer.Add(gainText, 0, wx.ALIGN_LEFT | wx.LEFT, 9) # Gain Slider outputSizer.Add(3, 1, 0) self.gainSlider = ControlSlider(self.outputPanel, -48, 18, 0, size=(220, 15), log=False, backColour=BACKGROUND_COLOUR, outFunction=self.onChangeGain) self.gainSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.gainSlider, TT_GAIN_SLIDER) CeciliaLib.setVar("gainSlider", self.gainSlider) outputSizer.Add(self.gainSlider, 0, wx.ALIGN_LEFT | wx.LEFT | wx.BOTTOM, 7) # VU Meter self.meterSizer = wx.BoxSizer() self.vuMeter = VuMeter(self.outputPanel) self.meterSizer.Add(self.vuMeter, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.LEFT | wx.BOTTOM, 8) CeciliaLib.getVar("audioServer").setAmpCallable(self.vuMeter) # Channels choice self.lineSizer = wx.BoxSizer(wx.HORIZONTAL) formatSizer = wx.BoxSizer(wx.VERTICAL) self.formatText = wx.StaticText(self.outputPanel, -1, 'Channels :') self.formatText.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) self.formatText.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) formatSizer.Add(self.formatText, 0, wx.ALIGN_LEFT | wx.LEFT, 2) self.formatChoice = CustomMenu(self.outputPanel, choice=[str(x) for x in range(1, 37)], init=str(CeciliaLib.getVar("nchnls")), outFunction=self.onFormatChange, colour=CONTROLLABEL_BACK_COLOUR) CeciliaLib.setToolTip(self.formatChoice, TT_CHANNELS) formatSizer.Add(self.formatChoice, 0, wx.ALIGN_LEFT | wx.TOP, 1) self.lineSizer.Add(formatSizer, 0, wx.ALIGN_LEFT | wx.RIGHT, 10) # Peak peakSizer = wx.BoxSizer(wx.VERTICAL) self.peakText = wx.StaticText(self.outputPanel, -1, 'Peak :') self.peakText.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) self.peakText.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) peakSizer.Add(self.peakText, 0, wx.ALIGN_LEFT | wx.LEFT, 2) self.peakLabel = PeakLabel(self.outputPanel, label=self.peak, size=(100, 20), font=None, colour=CONTROLLABEL_BACK_COLOUR, gainSlider=self.gainSlider) CeciliaLib.setToolTip(self.peakLabel, TT_PEAK) peakSizer.Add(self.peakLabel, 0, wx.ALIGN_LEFT | wx.TOP, 1) self.lineSizer.Add(peakSizer, 0, wx.ALIGN_LEFT | wx.LEFT, 10) outputTextPanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) outToolbox.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) outputSizer.Add(self.meterSizer, 1, wx.EXPAND) outputSizer.Add(self.lineSizer, 0, wx.ALIGN_LEFT | wx.LEFT | wx.BOTTOM, 7) outputSizer.AddGrowableRow(9) self.outputPanel.SetSizer(outputSizer) def createPluginPanel(self): paramsTemplate = [[0, 0, 0, 0], [.25, 1, .5, 1], [.25, .7, 5000, 1], [1, 1000, 1, 1], [.5, .2, .5, 1], [1000, 1, -3, 1], [0, 0, 0, 1], [-20, 3, 0, 1], [-70, 0.005, .01, 1], [.7, .7, -12, 1], [8, 1, 0, 1], [100, 5, 1.1, 1], [.1, 0, 0.5, 1], [0.5, 0.25, 0.25, 1], [-7, 0, 0.5, 1], [80, 2.01, 0.33, 1], [80, 0.5, 0.33, 1], [0.025, 0.5, 1, 2]] self.pluginsParams = {} for i in range(NUM_OF_PLUGINS): CeciliaLib.setPlugins(None, i) self.pluginsParams[i] = copy.deepcopy(paramsTemplate) self.pluginsDict = {'None': NonePlugin, 'Reverb': ReverbPlugin, 'WGVerb': WGReverbPlugin, 'Filter': FilterPlugin, 'Chorus': ChorusPlugin, 'Para EQ': EQPlugin, '3 Bands EQ': EQ3BPlugin, 'Compress': CompressPlugin, 'Gate': GatePlugin, 'Disto': DistoPlugin, 'AmpMod': AmpModPlugin, 'Phaser': PhaserPlugin, 'Delay': DelayPlugin, 'Flange': FlangePlugin, 'Harmonizer': HarmonizerPlugin, 'Resonators': ResonatorsPlugin, 'DeadReson': DeadResonPlugin, 'ChaosMod': ChaosModPlugin} self.pluginsPanel = wx.Panel(self, -1, style=wx.BORDER_NONE) self.pluginsPanel.SetBackgroundColour(BACKGROUND_COLOUR) self.pluginSizer = wx.BoxSizer(wx.VERTICAL) pluginTextPanel = wx.Panel(self.pluginsPanel, -1, style=wx.BORDER_NONE) pluginTextPanel.SetBackgroundColour(TITLE_BACK_COLOUR) pluginTextSizer = wx.FlexGridSizer(1, 1, 0, 0) pluginText = wx.StaticText(pluginTextPanel, -1, 'POST-PROCESSING ') pluginText.SetFont(wx.Font(SECTION_TITLE_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) pluginText.SetBackgroundColour(TITLE_BACK_COLOUR) pluginText.SetForegroundColour(SECTION_TITLE_COLOUR) pluginTextSizer.Add(pluginText, 0, wx.ALIGN_RIGHT | wx.ALL, 3) pluginTextSizer.AddGrowableCol(0) pluginTextPanel.SetSizer(pluginTextSizer) self.pluginSizer.Add(pluginTextPanel, 0, wx.EXPAND, 0) self.pluginSizer.Add(5, 3, 0) self.bagSizer = wx.GridBagSizer(NUM_OF_PLUGINS * 2, 0) self.plugins = [] for i in range(NUM_OF_PLUGINS): plugin = NonePlugin(self.pluginsPanel, self.replacePlugin, i) self.bagSizer.Add(plugin, (i * 2, 0)) self.bagSizer.Add(Separator(self.pluginsPanel, (230, 2), colour=BORDER_COLOUR), (i * 2 + 1, 0)) self.plugins.append(plugin) self.pluginSizer.Add(self.bagSizer, 0) self.pluginsPanel.SetSizer(self.pluginSizer) def getCfileinList(self): return self.cfileinList def getCfileinFromName(self, name): good = None for cfilein in self.cfileinList: if name == cfilein.getName(): good = cfilein break return good def listenSoundfile(self): CeciliaLib.listenSoundfile(self.outputFilename) def editSoundfile(self): CeciliaLib.editSoundfile(self.outputFilename) def getNonZeroTime(self): return self.nonZeroTime def setTime(self, time, m, s, c): if time != 0: self.nonZeroTime = time self.time = time self.clocker.setTime(m, s, c) def resetMeter(self): self.updatePeak(0) self.resetVuMeter() def onPlayStop(self, value): if value: self.nonZeroTime = 0 CeciliaLib.setVar("toDac", True) CeciliaLib.getVar("grapher").toolbar.loadingMsg.SetForegroundColour("#FFFFFF") CeciliaLib.getVar("grapher").toolbar.loadingMsg.Refresh() wx.CallLater(50, CeciliaLib.startCeciliaSound, True) else: CeciliaLib.stopCeciliaSound() def onRec(self, value): if value: if self.outputFilename != '': filename = CeciliaLib.autoRename(self.outputFilename) self.filenameLabel.setLabel(CeciliaLib.shortenName(os.path.split(filename)[1], self.charNumForLabel)) if self.outputFilename == '': filename = self.onSelectOutputFilename() if filename is None: CeciliaLib.stopCeciliaSound() return self.outputFilename = filename CeciliaLib.setVar("outputFile", filename) self.nonZeroTime = 0 CeciliaLib.setVar("toDac", True) CeciliaLib.getVar("grapher").toolbar.loadingMsg.SetForegroundColour("#FFFFFF") CeciliaLib.getVar("grapher").toolbar.loadingMsg.Refresh() wx.CallLater(50, CeciliaLib.startCeciliaSound, True, True) else: CeciliaLib.stopCeciliaSound() def onBounceToDisk(self): if self.outputFilename != '': filename = CeciliaLib.autoRename(self.outputFilename) self.filenameLabel.setLabel(CeciliaLib.shortenName(os.path.split(filename)[1], self.charNumForLabel)) if self.outputFilename == '': filename = self.onSelectOutputFilename() if filename is None: CeciliaLib.stopCeciliaSound() return self.outputFilename = filename CeciliaLib.setVar("outputFile", filename) CeciliaLib.setVar("toDac", False) self.showBounceDialog() CeciliaLib.startCeciliaSound(timer=False) self.updatePeak(0) def showBounceDialog(self): self.bounce_dlg = wx.Dialog(CeciliaLib.getVar('interface'), -1, "Bounce to disk") sizer = wx.BoxSizer(wx.VERTICAL) self.bounce_label = wx.StaticText(self.bounce_dlg, -1, "Writing %s on disk..." % self.outputFilename) sizer.Add(self.bounce_label, 0, wx.ALIGN_CENTRE | wx.ALL, 25) self.bounce_dlg.SetSizerAndFit(sizer) self.bounce_dlg.CenterOnParent() self.bounce_dlg.Show() def closeBounceToDiskDialog(self): try: self.bounce_dlg.Destroy() except: pass def onBatchProcessing(self, filename): self.outputFilename = filename CeciliaLib.setVar("outputFile", filename) CeciliaLib.setVar("toDac", False) CeciliaLib.startCeciliaSound(timer=False) self.updatePeak(0) def onSelectOutputFilename(self): file = CeciliaLib.saveFileDialog(self, AUDIO_FILE_WILDCARD, type='Save audio') if file is not None: self.filenameLabel.setLabel(CeciliaLib.shortenName(os.path.split(file)[1], self.charNumForLabel)) self.outputFilename = file return file def updateOutputFormat(self): self.vuMeter.updateNchnls() x, y = self.meterSizer.GetPosition() w, h = self.vuMeter.GetSize() self.meterSizer.SetMinSize((w, h + 8)) self.meterSizer.SetDimension(x, y, w, h + 8) w2, h2 = self.lineSizer.GetSize() self.lineSizer.SetDimension(7, y + h + 10, w2, h2) self.Layout() wx.CallAfter(self.Refresh) def onFormatChange(self, idx, choice): nchnls = int(choice) CeciliaLib.setVar("nchnls", nchnls) self.updateOutputFormat() def onReuseOutputFile(self): if os.path.isfile(self.outputFilename): if self.cfileinList != []: self.cfileinList[0].updateMenuFromPath(self.outputFilename) def setTotalTime(self, time, force=False): if CeciliaLib.getVar("audioServer").isAudioServerRunning() and not force: self.tmpTotalTime = time else: if self.cfileinList != [] and time == 0: time = self.cfileinList[0].getDuration() self.durationSlider.SetValue(time) if CeciliaLib.getVar("grapher") and time != CeciliaLib.getVar("totalTime"): CeciliaLib.setVar("totalTime", time) CeciliaLib.getVar("grapher").setTotalTime(time) self.tmpTotalTime = time def updateDurationSlider(self): self.durationSlider.SetValue(CeciliaLib.getVar("totalTime")) def updateNchnls(self): self.formatChoice.setStringSelection(str(CeciliaLib.getVar("nchnls"))) self.updateOutputFormat() def onChangeGain(self, gain): CeciliaLib.getVar("audioServer").setAmp(gain) def updatePeak(self, peak): self.peak = peak * 90.0 - 90.0 label = '' if self.peak > 0: label += '+' label += '%2.2f dB' % self.peak self.peakLabel.setLabel(label) def resetVuMeter(self): self.vuMeter.resetMax() class CInputBase(wx.Panel): def __init__(self, parent, id=-1, label='', size=(-1, -1), style=wx.BORDER_NONE, name=''): wx.Panel.__init__(self, parent, id, size=size, style=style, name=name) self.SetBackgroundColour(BACKGROUND_COLOUR) self.samplerFrame = None self.label = label self.name = name self.duration = 0 self.chnls = 0 self.type = '' self.samprate = 0 self.bitrate = 0 self.filePath = '' self.folderInfo = None self.mode = 0 mainSizer = wx.FlexGridSizer(4, 1, 0, 0) mainSizer.Add(200, 4, 0) # Static label for the popup menu line1 = wx.BoxSizer(wx.HORIZONTAL) textLabel = wx.StaticText(self, -1, self.label + ' :') textLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) textLabel.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) line1.Add(textLabel, 0, wx.LEFT, 2) mainSizer.Add(line1, 0, wx.LEFT, 8) # Popup menu line2 = wx.BoxSizer(wx.HORIZONTAL) line2.Add(2, -1, 0) self.fileMenu = FolderPopup(self, path=None, init='', outFunction=self.onSelectSound, emptyFunction=self.onLoadFile, backColour=CONTROLLABEL_BACK_COLOUR) CeciliaLib.setToolTip(self.fileMenu, TT_SEL_SOUND) line2.Add(self.fileMenu, 0, wx.ALIGN_CENTER | wx.RIGHT, 6) self.modebutton = InputModeButton(self, 0, outFunction=self.onChangeMode) line2.Add(self.modebutton, 0, wx.ALIGN_CENTER | wx.TOP, 2) self.toolbox = ToolBox(self, tools=['play', 'edit', 'open'], outFunction=[self.listenSoundfile, self.editSoundfile, self.onShowSampler]) CeciliaLib.setToolTip(self.toolbox, TT_INPUT_TOOLS) self.toolbox.setOpen(False) line2.Add(self.toolbox, 0, wx.ALIGN_CENTER | wx.TOP | wx.LEFT, 2) mainSizer.Add(line2, 1, wx.LEFT, 6) mainSizer.Add(5, 2, 0) self.createSamplerFrame() self.SetSizer(mainSizer) CeciliaLib.getVar("userInputs")[self.name] = dict() CeciliaLib.getVar("userInputs")[self.name]['path'] = '' def enable(self, state): self.toolbox.enable(state) def onChangeMode(self, value): if not CeciliaLib.getVar("audioServer").isAudioServerRunning(): self.mode = value CeciliaLib.getVar("userInputs")[self.name]['mode'] = self.mode self.processMode() def getMode(self): return self.mode def onShowSampler(self): if self.mode != 1: if self.samplerFrame.IsShown(): self.samplerFrame.Hide() else: pos = wx.GetMousePosition() framepos = (pos[0] + 10, pos[1] + 20) self.samplerFrame.SetPosition(framepos) self.samplerFrame.Show() def getDuration(self): return self.duration def setTotalTime(self): if self.duration: CeciliaLib.getControlPanel().setTotalTime(self.duration) CeciliaLib.getControlPanel().updateDurationSlider() def reset(self): self.fileMenu.reset() self.filePath = '' CeciliaLib.getVar("userInputs")[self.name]['path'] = self.filePath def reinitSamplerFrame(self): try: self.samplerFrame.reinit() except: pass def getSoundInfos(self, file): file = CeciliaLib.ensureNFD(file) self.filePath = CeciliaLib.ensureNFD(self.folderInfo[file]['path']) self.duration = self.folderInfo[file]['dur'] self.chnls = self.folderInfo[file]['chnls'] self.type = self.folderInfo[file]['type'] self.samprate = self.folderInfo[file]['samprate'] self.bitrate = self.folderInfo[file]['bitrate'] self.samplerFrame.offsetSlider.Enable() self.samplerFrame.offsetSlider.SetRange(0, self.duration) self.samplerFrame.offsetSlider.SetValue(self.getOffset()) self.samplerFrame.update(path=self.filePath, dur=self.duration, type=self.type, bitDepth=self.bitrate, chanNum=self.chnls, sampRate=self.samprate) CeciliaLib.getVar("userInputs")[self.name]['sr%s' % self.name] = self.samprate CeciliaLib.getVar("userInputs")[self.name]['dur%s' % self.name] = self.duration CeciliaLib.getVar("userInputs")[self.name]['nchnls%s' % self.name] = self.chnls CeciliaLib.getVar("userInputs")[self.name]['off%s' % self.name] = self.getOffset() CeciliaLib.getVar("userInputs")[self.name]['path'] = self.filePath def onLoadFile(self, filePath=''): if filePath == '': path = CeciliaLib.openAudioFileDialog(self, AUDIO_FILE_WILDCARD, defaultPath=CeciliaLib.getVar("openAudioFilePath", unicode=True)) elif not os.path.isfile(filePath): return False else: path = filePath if path is None: return False if not CeciliaLib.getVar("audioServer").validateAudioFile(path): CeciliaLib.showErrorDialog("Unable to retrieve sound infos", "There is something wrong with this file, please select another one.") return False if path: self.updateMenuFromPath(path) lastfiles = CeciliaLib.getVar("lastAudioFiles") if lastfiles != "": lastfiles = lastfiles.split(";") else: lastfiles = [] if path in lastfiles: return True if len(lastfiles) >= 10: lastfiles = lastfiles[1:] lastfiles.append(path) lastfiles = ";".join(lastfiles) CeciliaLib.setVar("lastAudioFiles", lastfiles) return True else: return False def updateMenuFromPath(self, path): if os.path.isfile(path): root = os.path.split(path)[0] isfile = True elif os.path.isdir(path): root = path isfile = False pathList = [] for p in os.listdir(root): pathList.append(os.path.join(root, p)) self.folderInfo = CeciliaLib.getVar("audioServer").getSoundsFromList(pathList) files = list(self.folderInfo.keys()) files.sort() self.fileMenu.setChoice(files) if isfile: self.fileMenu.setLabel(CeciliaLib.ensureNFD(os.path.split(path)[1])) else: self.fileMenu.setLabel(CeciliaLib.ensureNFD(files[0])) def listenSoundfile(self): CeciliaLib.listenSoundfile(self.filePath) def editSoundfile(self): CeciliaLib.editSoundfile(self.filePath) def onOffsetSlider(self, value): CeciliaLib.getVar("userInputs")[self.name]['off%s' % self.name] = value if self.mode >= 2: newMaxDur = value elif self.duration is not None: newMaxDur = self.duration - value CeciliaLib.getVar("userInputs")[self.name]['dur%s' % self.name] = newMaxDur try: self.samplerFrame.loopInSlider.setRange(0, newMaxDur) self.samplerFrame.loopOutSlider.setRange(0, newMaxDur) except: pass def setOffset(self, value): CeciliaLib.getVar("userInputs")[self.name]['off%s' % self.name] = value self.samplerFrame.offsetSlider.Enable() self.samplerFrame.offsetSlider.SetValue(value) def getOffset(self): return self.samplerFrame.offsetSlider.GetValue() def getName(self): return self.name def getSamplerFrame(self): return self.samplerFrame class Cfilein(CInputBase): def __init__(self, parent, id=-1, label='', size=(-1, -1), style=wx.BORDER_NONE, name=''): CInputBase.__init__(self, parent, id, label=label, size=size, style=style, name=name) CeciliaLib.getVar("userInputs")[self.name]['type'] = 'cfilein' def processMode(self): if self.mode in [1, 3]: self.mode = (self.mode + 1) % 4 self.modebutton.setValue(self.mode) CeciliaLib.getVar("userInputs")[self.name]['mode'] = self.mode if self.mode == 0: self.fileMenu.setEnable(True) self.samplerFrame.textOffset.SetLabel('Offset :') self.samplerFrame.offsetSlider.SetValue(0) self.samplerFrame.liveInputHeader(False) elif self.mode == 2: self.fileMenu.setEnable(False) self.samplerFrame.textOffset.SetLabel('Table Length (sec) :') self.samplerFrame.offsetSlider.Enable() self.samplerFrame.offsetSlider.SetValue(5) self.samplerFrame.liveInputHeader(True) def createSamplerFrame(self): self.samplerFrame = CfileinFrame(self, self.name) def onSelectSound(self, idx, file): self.getSoundInfos(file) class CSampler(CInputBase): def __init__(self, parent, id=-1, label='', size=(-1, -1), style=wx.BORDER_NONE, name=''): CInputBase.__init__(self, parent, id, label=label, size=size, style=style, name=name) CeciliaLib.getVar("userInputs")[self.name]['type'] = 'csampler' def processMode(self): grapher = CeciliaLib.getVar('grapher') if self.mode == 0: self.fileMenu.setEnable(True) grapher.setSamplerLineStates(self.name, True) self.samplerFrame.textOffset.SetLabel('Offset :') self.samplerFrame.offsetSlider.SetValue(0) self.samplerFrame.liveInputHeader(False) elif self.mode == 1: self.fileMenu.setEnable(False) grapher.setSamplerLineStates(self.name, False) if self.samplerFrame.IsShown(): self.samplerFrame.Hide() self.toolbox.setOpen(False) else: grapher.setSamplerLineStates(self.name, True) self.samplerFrame.textOffset.SetLabel('Table Length (sec) :') self.samplerFrame.offsetSlider.Enable() self.samplerFrame.offsetSlider.SetValue(5) self.samplerFrame.loopInSlider.setValue(0) self.samplerFrame.loopOutSlider.setValue(5) self.samplerFrame.liveInputHeader(True, self.mode) def createSamplerFrame(self): self.samplerFrame = SamplerFrame(self, self.name) def onSelectSound(self, idx, file): self.getSoundInfos(file) for line in CeciliaLib.getVar("grapher").plotter.getData(): if line.getName() == self.samplerFrame.loopInSlider.getCName() or line.getName() == self.samplerFrame.loopOutSlider.getCName(): line.changeYrange((0, self.duration)) if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._samplers[self.name].setSound(self.filePath) def getSamplerInfo(self): info = {} info['loopMode'] = self.samplerFrame.getLoopMode() info['startFromLoop'] = self.samplerFrame.getStartFromLoop() info['loopX'] = self.samplerFrame.getLoopX() info['loopIn'] = self.samplerFrame.getLoopIn() info['loopOut'] = self.samplerFrame.getLoopOut() info['gain'] = self.samplerFrame.getGain() info['transp'] = self.samplerFrame.getTransp() info['xfadeshape'] = self.samplerFrame.getXfadeShape() return info def getSamplerSliders(self): return self.getSamplerFrame().sliderlist class BaseInputFrame(wx.Frame): def __init__(self, parent, name, pos=wx.DefaultPosition): style = (wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_FLOAT_ON_PARENT) wx.Frame.__init__(self, parent, title='', pos=pos, style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.name = name def OnLooseFocus(self, event): win = wx.FindWindowAtPointer() if win[0] is not None: if win[0].GetTopLevelParent() in [self, CeciliaLib.getVar("mainFrame")]: pass else: win = CeciliaLib.getVar("interface") win.Raise() def close(self): self.Hide() self.GetParent().toolbox.setOpen(False) def createHeader(self): if self.sampRate > 1000: self.sampRate = self.sampRate / 1000. header = '%s\n' % CeciliaLib.shortenName(self.path, 48) header += '%0.2f sec - %s - %s - %d ch. - %2.1fkHz' % (self.dur, self.type, self.bitDepth, self.chanNum, self.sampRate) return header class CfileinFrame(BaseInputFrame): def __init__(self, parent, name): BaseInputFrame.__init__(self, parent, name) self.SetClientSize((385, 143)) panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) w, h = self.GetSize() panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) # Header self.title = FrameLabel(panel, '', size=(w - 2, 50)) box.Add(self.title, 0, wx.ALL, 1) box.Add(200, 2, 0) #toolbox toolsBox = wx.BoxSizer(wx.HORIZONTAL) tools = ToolBox(panel, size=(80, 20), tools=['play', 'edit', 'time'], outFunction=[self.parent.listenSoundfile, self.parent.editSoundfile, self.parent.setTotalTime]) CeciliaLib.setToolTip(tools, TT_CFILEIN_TOOLS) toolsBox.Add(tools, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 320) box.Add(toolsBox, 0, wx.TOP, 5) # Static label for the offset slider self.textOffset = wx.StaticText(panel, -1, '%s Offset :' % self.parent.label) self.textOffset.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) self.textOffset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) self.textOffset.SetBackgroundColour(BACKGROUND_COLOUR) box.Add(self.textOffset, 0, wx.LEFT, 20) # Offset slider offBox = wx.BoxSizer(wx.HORIZONTAL) self.offsetSlider = ControlSlider(panel, minvalue=0, maxvalue=100, size=(222, 15), init=0, outFunction=self.parent.onOffsetSlider, backColour=BACKGROUND_COLOUR) self.offsetSlider.setSliderHeight(10) self.offsetSlider.Disable() offBox.Add(self.offsetSlider, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 20) box.Add(offBox, 0, wx.EXPAND) box.Add(200, 10, 0) self.close = CloseBox(panel, outFunction=self.close) box.Add(self.close, 0, wx.LEFT, 330) box.Add(200, 7, 0) panel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.title.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) panel.SetSizerAndFit(box) self.Show(False) def reinit(self): self.offsetSlider.setValue(0) def update(self, path, dur, type, bitDepth, chanNum, sampRate): self.path = path self.dur = dur self.type = type self.bitDepth = bitDepth self.chanNum = chanNum self.sampRate = sampRate soundInfoText = self.createHeader() self.title.setLabel(soundInfoText) def liveInputHeader(self, yes=True): if yes: self.title.setLabel("Audio table will be filled with live input.") else: self.title.setLabel("") class SamplerFrame(BaseInputFrame): def __init__(self, parent, name): BaseInputFrame.__init__(self, parent, name) w, h = 390, 300 self.size = (w, h) self.SetClientSize(self.size) self.dur = 0 self.loopList = ['Off', 'Forward', 'Backward', 'Back and Forth'] panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) # Header self.title = FrameLabel(panel, '', size=(w - 2, 50)) box.Add(self.title, 0, wx.ALL, 1) box.Add(200, 5, 0) # Static label for the offset slider self.textOffset = wx.StaticText(panel, -1, '%s Offset :' % self.parent.label) self.textOffset.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) self.textOffset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) self.textOffset.SetBackgroundColour(BACKGROUND_COLOUR) box.Add(self.textOffset, 0, wx.LEFT, 20) box.Add(200, 2, 0) # Offset slider offBox = wx.BoxSizer(wx.HORIZONTAL) self.offsetSlider = ControlSlider(panel, minvalue=0, maxvalue=100, size=(345, 15), init=0, outFunction=self.parent.onOffsetSlider, backColour=BACKGROUND_COLOUR) CeciliaLib.setToolTip(self.offsetSlider, TT_SAMPLER_OFFSET) self.offsetSlider.setSliderHeight(10) self.offsetSlider.Disable() offBox.Add(self.offsetSlider, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 20) box.Add(offBox, wx.EXPAND) box.Add(200, 10, 0) #Loop type + toolbox loopBox = wx.FlexGridSizer(1, 8, 3, 3) loopLabel = wx.StaticText(panel, -1, "Loop") loopLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) loopLabel.SetForegroundColour("#FFFFFF") loopBox.Add(loopLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 3) self.loopMenu = SamplerPopup(panel, self.loopList, self.loopList[1], self.name, outFunction=self.handleLoopMode) CeciliaLib.setToolTip(self.loopMenu.popup, TT_SAMPLER_LOOP) loopBox.Add(self.loopMenu.popup, 0, wx.ALIGN_CENTER_VERTICAL) startLabel = wx.StaticText(panel, -1, "Start from loop") startLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) startLabel.SetForegroundColour("#FFFFFF") loopBox.Add(startLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) self.startToggle = SamplerToggle(panel, 0, self.name) CeciliaLib.setToolTip(self.startToggle.toggle, TT_SAMPLER_START) loopBox.Add(self.startToggle.toggle, 0, wx.ALIGN_CENTER_VERTICAL) xfadeLabel = wx.StaticText(panel, -1, "Xfade") xfadeLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) xfadeLabel.SetForegroundColour("#FFFFFF") loopBox.Add(xfadeLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) self.xfadeSwitcher = XfadeSwitcher(panel, 0, outFunction=self.handleXfadeSwitch) CeciliaLib.setToolTip(self.xfadeSwitcher, TT_SAMPLER_XFADE_SHAPE) loopBox.Add(self.xfadeSwitcher, 0, wx.ALIGN_CENTER_VERTICAL) tools = ToolBox(panel, size=(80, 20), tools=['play', 'edit', 'time'], outFunction=[self.parent.listenSoundfile, self.parent.editSoundfile, self.parent.setTotalTime]) CeciliaLib.setToolTip(tools, TT_CFILEIN_TOOLS) loopBox.Add(tools, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) loopBox.AddGrowableCol(2) box.Add(loopBox, 0, wx.ALL, 10) # Sliders slidersBox = wx.FlexGridSizer(5, 4, 5, 5) self.loopInSlider = SamplerSlider(panel, self.name, "Loop In", "sec", 0, 1, 0, outFunction=self.handleLoopIn) CeciliaLib.setToolTip(self.loopInSlider.slider, TT_SAMPLER_LOOP_IN) CeciliaLib.setToolTip(self.loopInSlider.buttons, TT_SAMPLER_AUTO) slidersBox.AddMany([(self.loopInSlider.labelText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT), (self.loopInSlider.buttons, 0, wx.CENTER), (self.loopInSlider.slider, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5), (self.loopInSlider.unit, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)]) self.loopOutSlider = SamplerSlider(panel, self.name, "Loop Time", "sec", 0, 1, 1, outFunction=self.handleLoopOut) CeciliaLib.setToolTip(self.loopOutSlider.slider, TT_SAMPLER_LOOP_DUR) CeciliaLib.setToolTip(self.loopOutSlider.buttons, TT_SAMPLER_AUTO) slidersBox.AddMany([(self.loopOutSlider.labelText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT), (self.loopOutSlider.buttons, 0, wx.CENTER), (self.loopOutSlider.slider, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5), (self.loopOutSlider.unit, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)]) self.loopXSlider = SamplerSlider(panel, self.name, "Loop X", "%", 0, 50, 1, outFunction=self.handleLoopX) CeciliaLib.setToolTip(self.loopXSlider.slider, TT_SAMPLER_CROSSFADE) CeciliaLib.setToolTip(self.loopXSlider.buttons, TT_SAMPLER_AUTO) slidersBox.AddMany([(self.loopXSlider.labelText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT), (self.loopXSlider.buttons, 0, wx.CENTER), (self.loopXSlider.slider, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5), (self.loopXSlider.unit, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)]) self.gainSlider = SamplerSlider(panel, self.name, "Gain", "dB", -48, 18, 0, outFunction=self.handleGain) CeciliaLib.setToolTip(self.gainSlider.slider, TT_SAMPLER_GAIN) CeciliaLib.setToolTip(self.gainSlider.buttons, TT_SAMPLER_AUTO) slidersBox.AddMany([(self.gainSlider.labelText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT), (self.gainSlider.buttons, 0, wx.CENTER), (self.gainSlider.slider, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5), (self.gainSlider.unit, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)]) self.transpSlider = SamplerSlider(panel, self.name, "Transpo", "cents", -48, 48, 0, integer=False, outFunction=self.handleTransp) CeciliaLib.setToolTip(self.transpSlider.slider, TT_SAMPLER_TRANSPO) CeciliaLib.setToolTip(self.transpSlider.buttons, TT_SAMPLER_AUTO) slidersBox.AddMany([(self.transpSlider.labelText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT), (self.transpSlider.buttons, 0, wx.CENTER), (self.transpSlider.slider, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5), (self.transpSlider.unit, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)]) box.Add(slidersBox, 0, wx.EXPAND | wx.ALL, 6) self.close = CloseBox(panel, outFunction=self.close) box.Add(self.close, 0, wx.LEFT, 330) box.Add(200, 7, 0) self.sliderlist = [self.loopInSlider, self.loopOutSlider, self.loopXSlider, self.gainSlider, self.transpSlider] samplerSliders = CeciliaLib.getVar("samplerSliders") CeciliaLib.setVar("samplerSliders", samplerSliders + self.sliderlist) userSliders = CeciliaLib.getVar("userSliders") CeciliaLib.setVar("userSliders", userSliders + self.sliderlist) samplerTogPop = CeciliaLib.getVar("samplerTogglePopup") CeciliaLib.setVar("samplerTogglePopup", samplerTogPop + [self.loopMenu, self.startToggle]) panel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.title.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) panel.SetSizerAndFit(box) self.Show(False) def reinit(self): for slider in self.sliderlist: slider.setMidiCtl(None) slider.setMidiChannel(1) slider.setOpenSndCtrl(None) slider.setOSCOut(None) slider.setPlay(False) slider.setRec(False) self.offsetSlider.SetValue(0) self.loopInSlider.setValue(0) self.loopOutSlider.setValue(self.dur) self.loopXSlider.setValue(1) self.gainSlider.setValue(0) self.transpSlider.setValue(0) self.setXfadeShape(0) self.setLoopMode(1) self.setStartFromLoop(0) self.setLoopX([1, 0]) def update(self, path, dur, type, bitDepth, chanNum, sampRate): self.path = path self.dur = dur self.type = type self.bitDepth = bitDepth self.chanNum = chanNum self.sampRate = sampRate soundInfoText = self.createHeader() self.title.setLabel(soundInfoText) self.loopInSlider.setRange(0, self.dur) self.loopInSlider.setValue(0) self.loopOutSlider.setRange(0, self.dur) self.loopOutSlider.setValue(self.dur) def liveInputHeader(self, yes=True, mode=2): if yes: if mode == 2: self.title.setLabel("Audio table will be filled with live input.") else: self.title.setLabel("Audio table (double buffered) will be continuously filled with live input.") else: self.title.setLabel("") def handleXfadeSwitch(self, value): if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._samplers[self.name].setXfadeShape(value) def setXfadeShape(self, value): self.xfadeSwitcher.setValue(value) def getXfadeShape(self): return self.xfadeSwitcher.getValue() def handleLoopMode(self, value): if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._samplers[self.name].setLoopMode(value) def setLoopMode(self, index): self.loopMenu.popup.setByIndex(index) def getLoopMode(self): return self.loopMenu.getValue() def setStartFromLoop(self, value): self.startToggle.setValue(value) def getStartFromLoop(self): return self.startToggle.getValue() def handleLoopX(self, value): if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._samplers[self.name].setXfade(value) def setLoopX(self, values): self.loopXSlider.setValue(values[0]) self.loopXSlider.setPlay(values[1]) if len(values) > 3: self.loopXSlider.setMidiCtl(values[4]) self.loopXSlider.setMidiChannel(values[5]) if values[6] is not None: self.loopXSlider.setOpenSndCtrl("%d:%s" % (values[6][0], values[6][1])) else: self.loopXSlider.setOpenSndCtrl(None) if values[7] is not None: self.loopXSlider.setOSCOut("%s:%d:%s" % (values[7][0], values[7][1], values[7][2])) else: self.loopXSlider.setOSCOut(None) else: self.loopXSlider.setMidiCtl(None) self.loopXSlider.setMidiChannel(1) self.loopXSlider.setOpenSndCtrl(None) self.loopXSlider.setOSCOut(None) def getLoopX(self): return [self.loopXSlider.getValue(), self.loopXSlider.getPlay(), self.loopXSlider.getRec(), self.loopXSlider.getWithMidi(), self.loopXSlider.getMidiCtl(), self.loopXSlider.getMidiChannel(), self.loopXSlider.getOpenSndCtrl(), self.loopXSlider.getOSCOut()] def handleLoopIn(self, value): if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._samplers[self.name].setStart(value) def setLoopIn(self, values): self.loopInSlider.setValue(values[0]) self.loopInSlider.setPlay(values[1]) if len(values) > 3: self.loopInSlider.setMidiCtl(values[4]) self.loopInSlider.setMidiChannel(values[5]) self.loopInSlider.slider.SetRange(values[6], values[7]) if values[8] is not None: self.loopInSlider.setOpenSndCtrl("%d:%s" % (values[8][0], values[8][1])) else: self.loopInSlider.setOpenSndCtrl(None) if values[9] is not None: self.loopInSlider.setOSCOut("%s:%d:%s" % (values[9][0], values[9][1], values[9][2])) else: self.loopInSlider.setOSCOut(None) else: self.loopInSlider.setMidiCtl(None) self.loopInSlider.setMidiChannel(1) self.loopInSlider.setOpenSndCtrl(None) self.loopInSlider.setOSCOut(None) def getLoopIn(self): return [self.loopInSlider.getValue(), self.loopInSlider.getPlay(), self.loopInSlider.getRec(), self.loopInSlider.getWithMidi(), self.loopInSlider.getMidiCtl(), self.loopInSlider.getMidiChannel(), self.loopInSlider.slider.getMinValue(), self.loopInSlider.slider.getMaxValue(), self.loopInSlider.getOpenSndCtrl(), self.loopInSlider.getOSCOut()] def handleLoopOut(self, value): if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._samplers[self.name].setDur(value) def setLoopOut(self, values): self.loopOutSlider.setValue(values[0]) self.loopOutSlider.setPlay(values[1]) if len(values) > 3: self.loopOutSlider.setMidiCtl(values[4]) self.loopOutSlider.setMidiChannel(values[5]) self.loopOutSlider.slider.SetRange(values[6], values[7]) if values[8] is not None: self.loopOutSlider.setOpenSndCtrl("%d:%s" % (values[8][0], values[8][1])) else: self.loopOutSlider.setOpenSndCtrl(None) if values[9] is not None: self.loopOutSlider.setOSCOut("%s:%d:%s" % (values[9][0], values[9][1], values[9][2])) else: self.loopOutSlider.setOSCOut(None) else: self.loopOutSlider.setMidiCtl(None) self.loopOutSlider.setMidiChannel(1) self.loopOutSlider.setOpenSndCtrl(None) self.loopOutSlider.setOSCOut(None) def getLoopOut(self): return [self.loopOutSlider.getValue(), self.loopOutSlider.getPlay(), self.loopOutSlider.getRec(), self.loopOutSlider.getWithMidi(), self.loopOutSlider.getMidiCtl(), self.loopOutSlider.getMidiChannel(), self.loopOutSlider.slider.getMinValue(), self.loopOutSlider.slider.getMaxValue(), self.loopOutSlider.getOpenSndCtrl(), self.loopOutSlider.getOSCOut()] def handleGain(self, value): if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._samplers[self.name].setGain(value) def setGain(self, values): self.gainSlider.setValue(values[0]) self.gainSlider.setPlay(values[1]) if len(values) > 3: self.gainSlider.setMidiCtl(values[4]) self.gainSlider.setMidiChannel(values[5]) if values[6] is not None: self.gainSlider.setOpenSndCtrl("%d:%s" % (values[6][0], values[6][1])) else: self.gainSlider.setOpenSndCtrl(None) if values[7] is not None: self.gainSlider.setOSCOut("%s:%d:%s" % (values[7][0], values[7][1], values[7][2])) else: self.gainSlider.setOSCOut(None) else: self.gainSlider.setMidiCtl(None) self.gainSlider.setMidiChannel(1) self.gainSlider.setOpenSndCtrl(None) self.gainSlider.setOSCOut(None) def getGain(self): return [self.gainSlider.getValue(), self.gainSlider.getPlay(), self.gainSlider.getRec(), self.gainSlider.getWithMidi(), self.gainSlider.getMidiCtl(), self.gainSlider.getMidiChannel(), self.gainSlider.getOpenSndCtrl(), self.gainSlider.getOSCOut()] def handleTransp(self, value): if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._samplers[self.name].setPitch(value) def setTransp(self, values): self.transpSlider.setValue(values[0]) self.transpSlider.setPlay(values[1]) if len(values) > 3: self.transpSlider.setMidiCtl(values[4]) self.transpSlider.setMidiChannel(values[5]) if values[6] is not None: self.transpSlider.setOpenSndCtrl("%d:%s" % (values[6][0], values[6][1])) else: self.transpSlider.setOpenSndCtrl(None) if values[7] is not None: self.transpSlider.setOSCOut("%s:%d:%s" % (values[7][0], values[7][1], values[7][2])) else: self.transpSlider.setOSCOut(None) else: self.transpSlider.setMidiCtl(None) self.transpSlider.setMidiChannel(1) self.transpSlider.setOpenSndCtrl(None) self.transpSlider.setOSCOut(None) def getTransp(self): return [self.transpSlider.getValue(), self.transpSlider.getPlay(), self.transpSlider.getRec(), self.transpSlider.getWithMidi(), self.transpSlider.getMidiCtl(), self.transpSlider.getMidiChannel(), self.transpSlider.getOpenSndCtrl(), self.transpSlider.getOSCOut()] class SamplerPlayRecButtons(wx.Panel): def __init__(self, parent, id=wx.ID_ANY, pos=(0, 0), size=(40, 20)): wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.playColour = SLIDER_PLAY_COLOUR_HOT self.recColour = SLIDER_REC_COLOUR_HOT self.enterWithButtonDown = False self.playOver = False self.recOver = False self.playOverWait = True self.recOverWait = True self.play = False self.rec = False if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def setOverWait(self, which): if which == 0: self.playOverWait = False elif which == 1: self.recOverWait = False def checkForOverReady(self, pos): if not wx.Rect(2, 2, 17, 17).Contains(pos): self.playOverWait = True if not wx.Rect(21, 2, 38, 17).Contains(pos): self.recOverWait = True def setPlay(self, x): if x == 0: self.play = False self.playColour = SLIDER_PLAY_COLOUR_HOT elif x == 1: if self.rec: self.setRec(0) self.play = True self.playColour = SLIDER_PLAY_COLOUR_NO_BIND wx.CallAfter(self.Refresh) def setRec(self, x): if x == 0: self.rec = False self.recColour = SLIDER_REC_COLOUR_HOT else: if self.play: self.setPlay(0) self.rec = True self.recColour = SLIDER_REC_COLOUR_PRESSED def MouseDown(self, evt): pos = evt.GetPosition() if wx.Rect(2, 2, 17, 17).Contains(pos): if self.play: self.setPlay(0) else: self.setPlay(1) self.setOverWait(0) elif wx.Rect(21, 2, 38, 17).Contains(pos): if self.rec: self.setRec(0) else: self.setRec(1) self.setOverWait(1) self.playOver = False self.recOver = False wx.CallAfter(self.Refresh) evt.Skip() def OnEnter(self, evt): if evt.LeftIsDown() and not CeciliaLib.getVar("audioServer").isAudioServerRunning(): self.enterWithButtonDown = True pos = evt.GetPosition() if wx.Rect(0, 0, 20, 20).Contains(pos): if self.play: self.setPlay(0) else: self.setPlay(1) elif wx.Rect(20, 0, 40, 20).Contains(pos): if self.rec: self.setRec(False) else: self.setRec(True) self.playOver = False self.recOver = False wx.CallAfter(self.Refresh) evt.Skip() def MouseUp(self, evt): self.enterWithButtonDown = False def OnMotion(self, evt): if not CeciliaLib.getVar("audioServer").isAudioServerRunning() and not self.enterWithButtonDown: pos = evt.GetPosition() if wx.Rect(2, 2, 17, 17).Contains(pos) and self.playOverWait: self.playOver = True self.recOver = False elif wx.Rect(21, 2, 38, 17).Contains(pos) and self.recOverWait: self.playOver = False self.recOver = True self.checkForOverReady(pos) wx.CallAfter(self.Refresh) evt.Skip() def OnLeave(self, evt): self.enterWithButtonDown = False self.playOver = False self.recOver = False self.playOverWait = True self.recOverWait = True wx.CallAfter(self.Refresh) evt.Skip() def OnPaint(self, evt): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) # Draw triangle if self.playOver: playColour = SLIDER_PLAY_COLOUR_OVER else: playColour = self.playColour gc.SetPen(wx.Pen(playColour, width=1, style=wx.SOLID)) gc.SetBrush(wx.Brush(playColour, wx.SOLID)) tri = [(14, h / 2), (9, 6), (9, h - 6), (14, h / 2)] gc.DrawLines(tri) dc.SetPen(wx.Pen('#333333', width=1, style=wx.SOLID)) dc.DrawLine(w // 2, 4, w // 2, h - 4) # Draw circle if self.recOver: recColour = SLIDER_REC_COLOUR_OVER else: recColour = self.recColour gc.SetPen(wx.Pen(recColour, width=1, style=wx.SOLID)) gc.SetBrush(wx.Brush(recColour, wx.SOLID)) gc.DrawEllipse(w / 4 + w / 2 - 4, h / 2 - 4, 8, 8) evt.Skip() def getPlay(self): return self.play def getRec(self): return self.rec class SamplerControlSlider(ControlSlider): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(200, 16), log=False, outFunction=None, integer=False, powoftwo=False, backColour=None, orient=wx.HORIZONTAL): ControlSlider.__init__(self, parent, minvalue, maxvalue, init, pos, size, log, outFunction, integer, powoftwo, backColour, orient) self.midiLearn = False self.midictl = "" self.openSndCtrl = "" def setMidiCtl(self, str): self.midictl = str self.midiLearn = False wx.CallAfter(self.Refresh) def inMidiLearnMode(self): self.midiLearn = True wx.CallAfter(self.Refresh) def setOpenSndCtrl(self, str): self.openSndCtrl = str wx.CallAfter(self.Refresh) def OnPaint(self, evt): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(self.backgroundColour, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(self.backgroundColour, width=self.borderWidth, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) # Draw inner part if self._enable: sliderColour = "#99A7CC" else: sliderColour = "#BBBBBB" if self.orient == wx.VERTICAL: w2 = (w - self.sliderWidth) / 2 rec = wx.Rect(w2, 0, self.sliderWidth, h) brush = gc.CreateLinearGradientBrush(w2, 0, w2 + self.sliderWidth, 0, "#646986", sliderColour) else: h2 = self.sliderHeight / 4 rec = wx.Rect(0, h2, w, self.sliderHeight) brush = gc.CreateLinearGradientBrush(0, h2, 0, h2 + self.sliderHeight, "#646986", sliderColour) gc.SetBrush(brush) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2], rec[3], 2) dc.SetTextForeground('#FFFFFF') if CeciliaLib.getVar("systemPlatform").startswith("linux"): dc.SetFont(wx.Font(6, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT)) elif CeciliaLib.getVar("systemPlatform") == 'win32': dc.SetFont(wx.Font(6, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) else: dc.SetFont(wx.Font(9, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT)) if self.midiLearn: dc.DrawLabel("Move a MIDI controller...", wx.Rect(5, 0, 50, h), wx.ALIGN_CENTER_VERTICAL) elif self.openSndCtrl: dc.DrawLabel(self.openSndCtrl, wx.Rect(5, 0, w, h), wx.ALIGN_CENTER_VERTICAL) else: dc.DrawLabel(self.midictl, wx.Rect(5, 0, w, h), wx.ALIGN_CENTER_VERTICAL) # Draw knob if self._enable: knobColour = '#888888' else: knobColour = "#DDDDDD" if self.orient == wx.VERTICAL: rec = wx.Rect(0, self.pos - self.knobHalfSize, w, self.knobSize - 1) if self.selected: brush = wx.Brush('#333333', wx.SOLID) else: brush = gc.CreateLinearGradientBrush(0, 0, w, 0, "#323854", knobColour) gc.SetBrush(brush) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2], rec[3], 3) else: rec = wx.Rect(self.pos - self.knobHalfSize, 0, self.knobSize - 1, h) if self.selected: brush = wx.Brush('#333333', wx.SOLID) else: brush = gc.CreateLinearGradientBrush(self.pos - self.knobHalfSize, 0, self.pos + self.knobHalfSize, 0, "#323854", knobColour) gc.SetBrush(brush) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2], rec[3], 3) if CeciliaLib.getVar("systemPlatform").startswith("linux") or CeciliaLib.getVar("systemPlatform") == 'win32': dc.SetFont(wx.Font(7, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) else: dc.SetFont(wx.Font(10, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) # Draw text if self.selected and self.new: val = self.new else: if self.integer: val = '%d' % self.GetValue() elif abs(self.GetValue()) >= 1000: val = '%.1f' % self.GetValue() elif abs(self.GetValue()) >= 100: val = '%.2f' % self.GetValue() elif abs(self.GetValue()) >= 10: val = '%.3f' % self.GetValue() elif abs(self.GetValue()) < 10: val = '%.4f' % self.GetValue() dc.SetTextForeground('#FFFFFF') dc.DrawLabel(val, rec, wx.ALIGN_CENTER) # Send value if self.outFunction and self.propagate: self.outFunction(self.GetValue()) self.propagate = True evt.Skip() class SamplerSlider: def __init__(self, parent, name, label, unit, mini, maxi, init, integer=False, outFunction=None): self.widget_type = "slider" self.name = name self.automationLength = None self.automationData = [] self.outFunction = outFunction self.label = name + ' ' + label self.cname = {'Loop In': name + 'start', 'Loop Time': name + 'end', 'Loop X': name + 'xfade', 'Gain': name + 'gain', 'Transpo': name + 'trans'}[label] self.path = os.path.join(AUTOMATION_SAVE_PATH, self.cname) self.convertSliderValue = 200 self.midictl = None self.midichan = 1 self.openSndCtrl = None self.OSCOut = None self.labelText = wx.StaticText(parent, -1, label) self.labelText.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) self.labelText.SetForegroundColour("#FFFFFF") self.labelText.Bind(wx.EVT_RIGHT_DOWN, self.onMidiLearn) self.labelText.Bind(wx.EVT_LEFT_DCLICK, self.onLabelDClick) self.buttons = SamplerPlayRecButtons(parent) self.slider = SamplerControlSlider(parent, mini, maxi, init, size=(236, 15), integer=integer, backColour=BACKGROUND_COLOUR, outFunction=self.sendValue) self.slider.setSliderHeight(10) self.unit = wx.StaticText(parent, -1, unit) self.unit.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) self.unit.SetForegroundColour("#FFFFFF") def setConvertSliderValue(self, x, end=None): self.convertSliderValue = x def getLog(self): return False def getMinValue(self): return self.slider.getMinValue() def getMaxValue(self): return self.slider.getMaxValue() def setAutomationLength(self, x): self.automationLength = x def getAutomationLength(self): return self.automationLength def sendValue(self, value): if not self.getPlay() or self.getRec(): if self.outFunction is not None: self.outFunction(value) def setRange(self, minval, maxval): self.slider.SetRange(minval, maxval) self.setValue(self.getValue()) wx.CallAfter(self.slider.Refresh) def setValue(self, val): self.slider.SetValue(val) def getValue(self): return self.slider.GetValue() def getLabel(self): return self.label def getCName(self): return self.cname def getName(self): return self.name def setPlay(self, x): self.buttons.setPlay(x) def getPlay(self): return self.buttons.getPlay() def setRec(self, x): self.buttons.setRec(x) def getRec(self): return self.buttons.getRec() def getPath(self): return self.path def setAutomationData(self, data): # convert values on scaling temp = [] log = self.getLog() minval = self.getMinValue() maxval = self.getMaxValue() automationlength = self.getAutomationLength() frac = automationlength / CeciliaLib.getVar("totalTime") virtuallength = len(data) / frac data.extend([data[-1]] * int(((1 - frac) * virtuallength))) totallength = float(len(data)) oldpos = 0 oldval = data[0] if log: maxOnMin = maxval / minval torec = math.log10(oldval / minval) / math.log10(maxOnMin) else: maxMinusMin = maxval - minval torec = (oldval - minval) / maxMinusMin temp.append([0.0, torec]) for i, val in enumerate(data): length = (i - oldpos) / totallength pos = oldpos / totallength + length if log: torec = math.log10(val / minval) / math.log10(maxOnMin) else: torec = (val - minval) / maxMinusMin temp.append([pos, torec]) oldval = val oldpos = i self.automationData = temp def getAutomationData(self): return [[x[0], x[1]] for x in self.automationData] def onMidiLearn(self, evt): if evt.ShiftDown(): self.setMidiCtl(None) elif CeciliaLib.getVar("useMidi"): CeciliaLib.getVar("audioServer").midiLearn(self) self.slider.inMidiLearnMode() def setMidiCtl(self, ctl): if ctl is None: self.midictl = None self.midichan = 1 self.slider.setMidiCtl('') else: self.midictl = int(ctl) self.slider.setMidiCtl("%d:%d" % (self.midictl, self.midichan)) self.openSndCtrl = None self.slider.setOpenSndCtrl('') def getMidiCtl(self): return self.midictl def setMidiChannel(self, chan): self.midichan = int(chan) def getMidiChannel(self): return self.midichan def getWithMidi(self): if self.getMidiCtl() is not None and CeciliaLib.getVar("useMidi"): return True else: return False def update(self, val): if not self.slider.HasCapture() and self.getPlay() != 1 and self.getWithMidi(): self.setValue(val) def onLabelDClick(self, evt): f = OSCPopupFrame(CeciliaLib.getVar('interface'), self) f.CenterOnParent() f.Show() def setOSCInput(self, value): self.setOpenSndCtrl(value) def setOSCOutput(self, value): self.setOSCOut(value) def setOpenSndCtrl(self, value): if value is None or value == "": self.openSndCtrl = None self.slider.setOpenSndCtrl("") else: sep = value.split(":") port = int(sep[0].strip()) address = str(sep[1].strip()) self.openSndCtrl = (port, address) self.slider.setOpenSndCtrl("%d:%s" % (port, address)) self.setMidiCtl(None) def setOSCOut(self, value): if value is None or value == "": self.OSCOut = None else: sep = value.split(":") host = str(sep[0].strip()) port = int(sep[1].strip()) address = str(sep[2].strip()) self.OSCOut = (host, port, address) def getOpenSndCtrl(self): return self.openSndCtrl def getOSCOut(self): return self.OSCOut def getWithOSC(self): if self.openSndCtrl is not None: return True else: return False cecilia5-5.4.1/Resources/DocFrame.py000066400000000000000000001311471372272363700172260ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import os, keyword, shutil import wx import wx.stc as stc from .constants import * from .API_interface import * import Resources.CeciliaLib as CeciliaLib _INTRO_TEXT = """ "Cecilia5 API Documentation" # What is a Cecilia module A Cecilia module is a python file (with the extension 'C5', associated to the application) containing a class named `Module`, within which the audio processing chain is developed, and a list called `Interface`, telling the software what are the graphical controls necessary for the proper operation of the module. the file can then be loaded by the application to apply the process on different audio signals, whether coming from sound files or from the microphone input. Processes used to manipulate the audio signal must be written with the Python's dedicated signal processing module 'pyo'. # API Documentation Structure This API is divided into two parts: firstly, there is the description of the parent class, named `BaseModule`, from which every module must inherit. This class implements a lot of features that ease the creation of a dsp chain. Then, the various available GUI elements (widgets) are presented. """ _EXAMPLE_1 = ''' ### EXAMPLE 1 ### # This example shows how to use the sampler to loop any soundfile from the disk. # A state-variable filter is then applied on the looped sound. class Module(BaseModule): """ "State Variable Filter" Description This module implements lowpass, bandpass and highpass filters in parallel and allow the user to interpolate on an axis lp -> bp -> hp. Sliders # Cutoff/Center Freq : Cutoff frequency for lp and hp (center freq for bp) # Filter Q : Q factor (inverse of bandwidth) of the filter # Type (lp->bp->hp) : Interpolating factor between filters # Dry / Wet : Mix between the original and the filtered signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Chords : Pitch interval between voices (chords), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.dsp = SVF(self.snd, self.freq, self.q, self.type) self.out = Interp(self.snd, self.dsp, self.drywet, mul=self.env) Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0, 1), (1, 1)], col="blue1"), cslider(name="freq", label="Cutoff/Center Freq", min=20, max=20000, init=1000, rel="log", unit="Hz", col="green1"), cslider(name="q", label="Filter Q", min=0.5, max=25, init=1, rel="log", unit="x", col="green2"), cslider(name="type", label="Type (lp->bp->hp)", min=0, max=1, init=0.5, rel="lin", unit="x", col="green3"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpoly() ] ''' _EXAMPLE_2 = ''' ### EXAMPLE 2 ### # This example shows how to load a sound in a table (RAM) in order to apply # non-streaming effects. Here a frequency self-modulated reader is used to # create new harmonics, in a way similar to waveshaping distortion. class Module(BaseModule): """ "Self-modulated frequency sound looper" Description This module loads a sound in a table and apply a frequency self-modulated playback of the content. A Frequency self-modulation occurs when the output sound of the playback is used to modulate the reading pointer speed. That produces new harmonics in a way similar to waveshaping distortion. Sliders # Transposition : Transposition, in cents, of the input sound # Feedback : Amount of self-modulation in sound playback # Filter Frequency : Frequency, in Hertz, of the filter # Filter Q : Q of the filter (inverse of the bandwidth) Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of the filter # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Chords : Pitch interval between voices (chords), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addFilein("snd") self.trfactor = CentsToTranspo(self.transpo, mul=self.polyphony_spread) self.freq = Sig(self.trfactor, mul=self.snd.getRate()) self.dsp = OscLoop(self.snd, self.freq, self.feed*0.0002, mul=self.polyphony_scaling * 0.5) self.mix = self.dsp.mix(self.nchnls) self.out = Biquad(self.mix, freq=self.filt_f, q=self.filt_q, type=self.filt_t_index, mul=self.env) def filt_t(self, index, value): self.out.type = index Interface = [ cfilein(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0, 1), (1, 1)], col="blue1"), cslider(name="transpo", label="Transposition", min=-4800, max=4800, init=0, unit="cnts", col="red1"), cslider(name="feed", label="Feedback", min=0, max=1, init=0.25, unit="x", col="purple1"), cslider(name="filt_f", label="Filter Frequency", min=20, max=18000, init=10000, rel="log", unit="Hz", col="green1"), cslider(name="filt_q", label="Filter Q", min=0.5, max=25, init=1, rel="log", unit="x", col="green2"), cpopup(name="filt_t", label="Filter Type", init="Lowpass", value=["Lowpass", "Highpass", "Bandpass", "Bandreject"], col="green1"), cpoly() ] ''' _COLOUR_TEXT = """ Colours Five colours, with four shades each, are available to build the interface. The colour should be given, as a string, to the `col` argument of a widget function. """ _COLOURS = """ red1 blue1 green1 purple1 orange1 red2 blue2 green2 purple2 orange2 red3 blue3 green3 purple3 orange3 red4 blue4 green4 purple4 orange4 """ _MODULES_TEXT = """ "Documentation of Built-In Modules" The built-in modules are classified into different categories: """ _CATEGORY_OVERVIEW = {'Dynamics': """ "Modules related to waveshaping and amplitude manipulations" """, 'Filters': """ "Filtering and subtractive synthesis modules" """, 'Multiband': """ "Various processing applied independently to four spectral regions" """, 'Pitch': """ "Modules related to playback speed and pitch manipulations" """, 'Resonators&Verbs': """ "Artificial spaces generation modules" """, 'Spectral': """ "Spectral streaming processing modules" """, 'Synthesis': """ "Additive synthesis and particle generators" """, 'Time': """ "Granulation based time-stretching and delay related modules" """} _MODULE_CATEGORIES = ['Dynamics', 'Filters', 'Multiband', 'Pitch', 'Resonators&Verbs', 'Spectral', 'Synthesis', 'Time'] _DOC_KEYWORDS = ['Attributes', 'Examples', 'Parameters', 'Methods', 'Notes', 'Methods details', 'Public', "BaseModule_API", "Interface_API", 'Notes', 'Overview', 'Initline', 'Description', 'Sliders', 'Graph Only', 'Popups & Toggles', 'Template', 'Colours', 'Public Attributes', 'Public Methods'] _KEYWORDS_LIST = ["cfilein", "csampler", "cpoly", "cgraph", "cslider", "crange", "csplitter", "ctoggle", "cpopup", "cbutton", "cgen"] _KEYWORDS_TREE = {"BaseModule_API": [], "Interface_API": ["cfilein", "csampler", "cpoly", "cgraph", "cslider", "crange", "csplitter", "ctoggle", "cpopup", "cbutton", "cgen"]} _NUM_PAGES = len(_KEYWORDS_LIST) DOC_STYLES = {'Default': {'default': '#000000', 'comment': '#003333', 'commentblock': '#000000', 'selback': '#CCCCCC', 'number': '#000000', 'string': '#000000', 'triple': '#000000', 'keyword': '#00007F', 'keyword2': '#003333', 'class': '#0000FF', 'function': '#007F7F', 'identifier': '#000000', 'caret': '#00007E', 'background': '#EEEEEE', 'linenumber': '#000000', 'marginback': '#B0B0B0', 'markerfg': '#CCCCCC', 'markerbg': '#000000', 'bracelight': '#AABBDD', 'bracebad': '#DD0000', 'lineedge': '#CCCCCC'}} if wx.Platform == '__WXMSW__': DOC_FACES = {'face': 'Verdana', 'size': 8, 'size2': 7} elif wx.Platform == '__WXMAC__': DOC_FACES = {'face': 'Monaco', 'size': 12, 'size2': 9} else: DOC_FACES = {'face': 'Monospace', 'size': 8, 'size2': 7} DOC_FACES['size3'] = DOC_FACES['size2'] + 4 DOC_FACES['size4'] = DOC_FACES['size2'] + 3 for key, value in DOC_STYLES['Default'].items(): DOC_FACES[key] = value DOC_STYLES_P = {'Default': {'default': '#000000', 'comment': '#007F7F', 'commentblock': '#7F7F7F', 'selback': '#CCCCCC', 'number': '#005000', 'string': '#7F007F', 'triple': '#7F0000', 'keyword': '#00007F', 'keyword2': '#007F9F', 'class': '#0000FF', 'function': '#007F7F', 'identifier': '#000000', 'caret': '#00007E', 'background': '#EEEEEE', 'linenumber': '#000000', 'marginback': '#B0B0B0', 'markerfg': '#CCCCCC', 'markerbg': '#000000', 'bracelight': '#AABBDD', 'bracebad': '#DD0000', 'lineedge': '#CCCCCC'}} if wx.Platform == '__WXMSW__': DOC_FACES_P = {'face': 'Verdana', 'size': 8, 'size2': 7} elif wx.Platform == '__WXMAC__': DOC_FACES_P = {'face': 'Monaco', 'size': 12, 'size2': 9} else: DOC_FACES_P = {'face': 'Monospace', 'size': 8, 'size2': 7} DOC_FACES_P['size3'] = DOC_FACES_P['size2'] + 4 for key, value in DOC_STYLES_P['Default'].items(): DOC_FACES_P[key] = value def _ed_set_style(editor, searchKey=None): editor.SetLexer(stc.STC_LEX_PYTHON) editor.SetKeyWords(0, " ".join(_KEYWORDS_LIST)) if searchKey is None: editor.SetKeyWords(1, " ".join(_DOC_KEYWORDS)) else: editor.SetKeyWords(1, " ".join(_DOC_KEYWORDS) + " " + searchKey) editor.SetMargins(5, 5) editor.SetSTCCursor(2) editor.SetIndent(4) editor.SetTabIndents(True) editor.SetTabWidth(4) editor.SetUseTabs(False) editor.StyleSetSpec(stc.STC_STYLE_DEFAULT, "fore:%(default)s, face:%(face)s, size:%(size)d, back:%(background)s" % DOC_FACES) editor.StyleClearAll() editor.StyleSetSpec(stc.STC_STYLE_DEFAULT, "fore:%(default)s, face:%(face)s, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "fore:%(linenumber)s, back:%(marginback)s, face:%(face)s, size:%(size2)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "fore:%(default)s, face:%(face)s" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_DEFAULT, "fore:%(default)s, face:%(face)s, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:%(comment)s, face:%(face)s, bold, italic, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_NUMBER, "fore:%(number)s, face:%(face)s, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_STRING, "fore:%(string)s, face:%(face)s, bold, size:%(size4)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_CHARACTER, "fore:%(string)s, face:%(face)s, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_WORD, "fore:%(keyword)s, face:%(face)s, bold, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_WORD2, "fore:%(keyword2)s, face:%(face)s, bold, size:%(size3)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_TRIPLE, "fore:%(triple)s, face:%(face)s, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:%(triple)s, face:%(face)s, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:%(class)s, face:%(face)s, bold, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_DEFNAME, "fore:%(function)s, face:%(face)s, bold, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_OPERATOR, "bold, size:%(size)d, face:%(face)s" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:%(identifier)s, face:%(face)s, size:%(size)d" % DOC_FACES) editor.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:%(commentblock)s, face:%(face)s, italic, size:%(size)d" % DOC_FACES) def _ed_set_style_p(editor, searchKey=None): editor.SetLexer(stc.STC_LEX_PYTHON) editor.SetKeyWords(0, " ".join(keyword.kwlist) + " None True False ") editor.SetMargins(5, 5) editor.SetSTCCursor(2) editor.SetIndent(4) editor.SetTabIndents(True) editor.SetTabWidth(4) editor.SetUseTabs(False) editor.StyleSetSpec(stc.STC_STYLE_DEFAULT, "fore:%(default)s, face:%(face)s, size:%(size)d, back:%(background)s" % DOC_FACES_P) editor.StyleClearAll() editor.StyleSetSpec(stc.STC_STYLE_DEFAULT, "fore:%(default)s, face:%(face)s, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "fore:%(linenumber)s, back:%(marginback)s, face:%(face)s, size:%(size2)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "fore:%(default)s, face:%(face)s" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_DEFAULT, "fore:%(default)s, face:%(face)s, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:%(comment)s, face:%(face)s, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_NUMBER, "fore:%(number)s, face:%(face)s, bold, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_STRING, "fore:%(string)s, face:%(face)s, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_CHARACTER, "fore:%(string)s, face:%(face)s, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_WORD, "fore:%(keyword)s, face:%(face)s, bold, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_WORD2, "fore:%(keyword2)s, face:%(face)s, bold, size:%(size3)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_TRIPLE, "fore:%(triple)s, face:%(face)s, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:%(triple)s, face:%(face)s, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:%(class)s, face:%(face)s, bold, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_DEFNAME, "fore:%(function)s, face:%(face)s, bold, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_OPERATOR, "bold, size:%(size)d, face:%(face)s" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:%(identifier)s, face:%(face)s, size:%(size)d" % DOC_FACES_P) editor.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:%(commentblock)s, face:%(face)s, size:%(size)d" % DOC_FACES_P) def complete_words_from_str(text, keyword): words = [keyword] keyword = keyword.lower() text_ori = text text = text.replace("`", " ").replace("'", " ").replace(".", " ").replace(", ", " ").replace('"', " ").replace("=", " ").replace("\n", " ").lower() found = text.find(keyword) while found > -1: start = text.rfind(" ", 0, found) end = text.find(" ", found) words.append(text_ori[start:end]) found = text.find(keyword, found + 1) words = " ".join(words) return words class ManualPanel(wx.Treebook): def __init__(self, parent): wx.Treebook.__init__(self, parent, -1, size=(600, 480), style=wx.BK_DEFAULT | wx.BORDER_SUNKEN) self.parent = parent self.searchKey = None self.Bind(wx.EVT_TREEBOOK_PAGE_CHANGED, self.OnPageChanged) def cleanup(self): self.searchKey = None self.DeleteAllPages() self.reset_history() def reset_history(self): self.fromToolbar = False self.oldPage = "" self.sequence = [] self.seq_index = 0 def AdjustSize(self): #self.GetTreeCtrl().InvalidateBestSize() self.SendSizeEvent() def copy(self): self.GetPage(self.GetSelection()).win.Copy() def collapseAll(self): count = self.GetPageCount() for i in range(count): if self.IsNodeExpanded(i): self.CollapseNode(i) def OnPageChanged(self, event): old = event.GetOldSelection() new = event.GetSelection() if new != old: text = self.GetPageText(new) self.getPage(text) event.Skip() def history_check(self): back = True forward = True if self.seq_index <= 0: back = False if self.seq_index == (len(self.sequence) - 1): forward = False self.parent.history_check(back, forward) def history_back(self): self.seq_index -= 1 if self.seq_index < 0: self.seq_index = 0 self.fromToolbar = True self.SetSelection(self.sequence[self.seq_index]) self.history_check() def history_forward(self): seq_len = len(self.sequence) self.seq_index += 1 if self.seq_index == seq_len: self.seq_index = seq_len - 1 self.fromToolbar = True self.SetSelection(self.sequence[self.seq_index]) self.history_check() def setStyle(self): return # TreeBook has no more a GetTreeCtrl method. Don't know how to retrieve it... tree = self.GetTreeCtrl() tree.SetBackgroundColour(DOC_STYLES['Default']['background']) root = tree.GetRootItem() tree.SetItemTextColour(root, DOC_STYLES['Default']['identifier']) (child, cookie) = tree.GetFirstChild(root) while child.IsOk(): tree.SetItemTextColour(child, DOC_STYLES['Default']['identifier']) if tree.ItemHasChildren(child): (child2, cookie2) = tree.GetFirstChild(child) while child2.IsOk(): tree.SetItemTextColour(child2, DOC_STYLES['Default']['identifier']) (child2, cookie2) = tree.GetNextChild(child, cookie2) (child, cookie) = tree.GetNextChild(root, cookie) modules_path = os.path.join(os.getcwd(), "doc-en", "source", "src", "modules") def prepare_doc_tree(): if os.path.isdir(modules_path): shutil.rmtree(modules_path) os.mkdir(modules_path) for cat in _MODULE_CATEGORIES: os.mkdir(os.path.join(modules_path, cat)) def create_modules_index(): lines = _MODULES_TEXT.splitlines(True) lines.pop(0) with open(os.path.join(modules_path, "index.rst"), "w") as f: f.write(lines[0].replace('"', '')) f.write("=" * len(lines[0])) f.write("\n") for i in range(1, len(lines)): f.write(lines[i]) f.write("\n.. toctree::\n :maxdepth: 2\n\n") for cat in _MODULE_CATEGORIES: f.write(" %s/index\n" % cat) def create_category_index(category, overview, modules): path = os.path.join(modules_path, category) lines = overview.splitlines(True) lines.pop(0) with open(os.path.join(path, "index.rst"), "w") as f: f.write(category + " : " + lines[0].replace('"', '')) f.write("=" * len(category + lines[0])) f.write("\n") for i in range(1, len(lines)): f.write(lines[i]) f.write("\n.. toctree::\n :maxdepth: 1\n\n") for mod in modules: f.write(" %s\n" % mod.split(".")[0]) def create_module_doc_page(module, text): root, name = os.path.split(module) root, category = os.path.split(root) name = name.split(".")[0] pname = name + ".rst" path = os.path.join(modules_path, category, pname) lines = text.splitlines(True) for i, line in enumerate(lines): if len(line) > 4: lines[i] = line[4:] with open(path, "w") as f: f.write(name + " : " + lines[0].replace('"', '')) f.write("=" * len(name + lines[0])) f.write("\n") tosub = 0 for i in range(1, len(lines)): if tosub > 0: f.write("-" * tosub) f.write("\n") if lines[i].strip() in _DOC_KEYWORDS: tosub = len(lines[i]) else: tosub = 0 if "#" in lines[i] and ":" in lines[i]: line = lines[i].replace("# ", "**").replace(" :", "** :") else: line = lines[i] f.write(line) class ManualPanel_modules(ManualPanel): def __init__(self, parent): ManualPanel.__init__(self, parent) self.root, self.directories, self.files = CeciliaLib.buildFileTree() self.parse() def parse(self): if BUILD_RST: prepare_doc_tree() self.cleanup() self.needToParse = True count = 1 win = self.makePanel("Modules") self.AddPage(win, "Modules") for key in self.directories: count += 1 win = self.makePanel(key) self.AddPage(win, key) for key2 in self.files[key]: count += 1 win = self.makePanel(os.path.join(self.root, key, key2)) self.AddSubPage(win, key2) self.setStyle() self.getPage("Modules") wx.CallLater(100, self.AdjustSize) def parseOnSearchName(self, keyword): self.cleanup() keyword = keyword.lower() for key in self.directories: for key2 in self.files[key]: if keyword in key2.lower(): win = self.makePanel(os.path.join(self.root, key, key2)) self.AddPage(win, key2) self.setStyle() wx.CallAfter(self.AdjustSize) def parseOnSearchPage(self, keyword): self.cleanup() keyword = keyword.lower() for key in self.directories: for key2 in self.files[key]: with open(os.path.join(self.root, key, key2), "r") as f: text = f.read().lower() first = text.find('"""') if first != -1: newline = text.find("\n", first) second = text.find('"""', newline) text = text[newline + 1:second] else: text = "module not documented..." if keyword in text: win = self.makePanel(os.path.join(self.root, key, key2)) self.AddPage(win, key2) self.setStyle() wx.CallAfter(self.AdjustSize) def makePanel(self, obj=None): panel = wx.Panel(self, -1) panel.isLoad = False if self.needToParse: if obj == "Modules": if BUILD_RST: create_modules_index() panel.win = stc.StyledTextCtrl(panel, -1, size=(600, 480), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.SetUseVerticalScrollBar(False) text = "" for cat in _MODULE_CATEGORIES: l = _CATEGORY_OVERVIEW[cat].splitlines()[1].replace('"', '').strip() text += "# %s\n %s\n" % (cat, l) panel.win.SetText(_MODULES_TEXT + text) elif obj in _MODULE_CATEGORIES: if BUILD_RST: create_category_index(obj, _CATEGORY_OVERVIEW[obj], self.files[obj]) panel.win = stc.StyledTextCtrl(panel, -1, size=(600, 480), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.SetUseVerticalScrollBar(False) text = _CATEGORY_OVERVIEW[obj] for file in self.files[obj]: text += "# %s\n" % file with open(os.path.join(self.root, obj, file), "r") as f: t = f.read() first = t.find('"""') if first != -1: newline = t.find("\n", first) second = t.find('\n', newline + 2) text += t[newline + 1:second].replace('"', '') text += "\n" panel.win.SetText(text) elif os.path.isfile(obj): with open(obj, "r") as f: text = f.read() first = text.find('"""') if first != -1: newline = text.find("\n", first) second = text.find('"""', newline) text = text[newline + 1:second] else: text = '"Module not documented..."' if BUILD_RST: create_module_doc_page(obj, text) obj = os.path.split(obj)[1] panel.win = stc.StyledTextCtrl(panel, -1, size=(600, 480), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.SetUseVerticalScrollBar(False) panel.win.SetText(text) else: var = eval(obj) if isinstance(var, str): panel.win = stc.StyledTextCtrl(panel, -1, size=(600, 480), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.SetUseVerticalScrollBar(False) panel.win.SetText(var) else: text = var.__doc__ panel.win = stc.StyledTextCtrl(panel, -1, size=(600, 480), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.SetUseVerticalScrollBar(False) panel.win.SetText(text) panel.win.SaveFile(CeciliaLib.ensureNFD(os.path.join(DOC_PATH, obj))) return panel def getPage(self, word): if word == self.oldPage: self.fromToolbar = False return page_count = self.GetPageCount() for i in range(page_count): text = self.GetPageText(i) if text == word: self.oldPage = word if not self.fromToolbar: self.sequence = self.sequence[0:self.seq_index + 1] self.sequence.append(i) self.seq_index = len(self.sequence) - 1 self.history_check() self.parent.setTitle(text) self.SetSelection(i) panel = self.GetPage(self.GetSelection()) if not panel.isLoad: panel.isLoad = True panel.win = stc.StyledTextCtrl(panel, -1, size=panel.GetSize(), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.LoadFile(os.path.join(CeciliaLib.ensureNFD(DOC_PATH), word)) panel.win.SetMarginWidth(1, 0) if self.searchKey is not None: words = complete_words_from_str(panel.win.GetText(), self.searchKey) _ed_set_style(panel.win, words) else: _ed_set_style(panel.win) panel.win.SetSelectionEnd(0) def OnPanelSize(evt, win=panel.win): win.SetPosition((0, 0)) win.SetSize(evt.GetSize()) panel.Bind(wx.EVT_SIZE, OnPanelSize) self.fromToolbar = False return try: win = self.makePanel(CeciliaLib.getVar("currentCeciliaFile")) self.AddPage(win, word) self.getPage(word) except: pass api_doc_path = os.path.join(os.getcwd(), "doc-en", "source", "src", "api") def prepare_api_doc_tree(): if os.path.isdir(api_doc_path): shutil.rmtree(api_doc_path) os.mkdir(api_doc_path) for cat in ["BaseModule", "Interface"]: os.mkdir(os.path.join(api_doc_path, cat)) def create_api_doc_index(): lines = _INTRO_TEXT.splitlines(True) lines.pop(0) with open(os.path.join(api_doc_path, "index.rst"), "w") as f: f.write(lines[0].replace('"', '')) f.write("=" * len(lines[0])) f.write("\n") tosub = 0 for i in range(1, len(lines)): if tosub > 0: f.write("-" * tosub) f.write("\n") if lines[i].startswith("#"): lines[i] = lines[i].replace("# ", "") tosub = len(lines[i]) else: tosub = 0 f.write(lines[i]) f.write("\n.. toctree::\n :maxdepth: 2\n\n") for cat in ["BaseModule", "Interface"]: f.write(" %s/index\n" % cat) def create_base_module_index(): lines = BaseModule_API.splitlines(True) lines.pop(0) with open(os.path.join(api_doc_path, "BaseModule", "index.rst"), "w") as f: f.write(lines[0].replace("_", " ")) f.write("=" * len(lines[0])) f.write("\n") tosub = 0 in_code_block = False for i in range(1, len(lines)): if in_code_block: if lines[i].startswith("###"): in_code_block = False lines[i] = "\n" else: lines[i] = " " + lines[i] else: if lines[i].startswith("###"): lines[i] = ".. code::\n\n" in_code_block = True if tosub > 0: f.write("-" * tosub) f.write("\n") if lines[i].startswith("#") and ":" not in lines[i]: lines[i] = lines[i].replace("# ", "") tosub = len(lines[i]) elif lines[i].startswith("#") and ":" in lines[i]: lines[i] = lines[i].replace("# ", "**").replace(" :", "**") elif lines[i].strip() in _DOC_KEYWORDS and not in_code_block: tosub = len(lines[i]) else: tosub = 0 f.write(lines[i]) def create_interface_api_index(): lines = Interface_API.splitlines(True) lines.pop(0) with open(os.path.join(api_doc_path, "Interface", "index.rst"), "w") as f: f.write(lines[0].replace("_", " ")) f.write("=" * len(lines[0])) f.write("\n") for i in range(1, len(lines)): f.write(lines[i]) f.write("\n.. toctree::\n :maxdepth: 1\n\n") for word in _KEYWORDS_LIST: f.write(" %s\n" % word) f.write(" colours\n") f.write(" example1\n") f.write(" example2\n") lines = _COLOUR_TEXT.splitlines(True) lines.pop(0) lines2 = _COLOURS.splitlines(True) lines2.pop(0) with open(os.path.join(api_doc_path, "Interface", "colours.rst"), "w") as f: f.write(lines[0]) f.write("=" * len(lines[0])) for i in range(1, len(lines)): f.write(lines[i]) f.write("\n\n.. code::\n\n") for i in range(len(lines2)): f.write(lines2[i] + "\n") lines = _EXAMPLE_1.splitlines(True) lines.pop(0) with open(os.path.join(api_doc_path, "Interface", "example1.rst"), "w") as f: f.write(lines[0].replace("###", "").strip().lower().capitalize() + "\n") f.write("=" * len(lines[0])) f.write("\n\n.. code::\n\n") for i in range(1, len(lines)): f.write(" " + lines[i]) lines = _EXAMPLE_2.splitlines(True) lines.pop(0) with open(os.path.join(api_doc_path, "Interface", "example2.rst"), "w") as f: f.write(lines[0].replace("###", "").strip().lower().capitalize() + "\n") f.write("=" * len(lines[0])) f.write("\n\n.. code::\n\n") for i in range(1, len(lines)): f.write(" " + lines[i]) def create_api_doc_page(obj, text): path = os.path.join(api_doc_path, "Interface", "%s.rst" % obj) lines = text.splitlines(True) lines.pop(0) for i, line in enumerate(lines): if len(line) > 4: lines[i] = line[4:] with open(path, "w") as f: f.write(obj + " : " + lines[0].replace('"', '').lower()) f.write("=" * len(obj + lines[0])) f.write("\n") tosub = 0 indent = 0 incode = False prompt = False for i in range(1, len(lines)): if tosub > 0: f.write("-" * tosub) f.write("\n") if incode: f.write("\n.. code::\n") incode = False if ">>> " in lines[i]: if not prompt: prompt = True f.write("\n.. code::\n\n") else: prompt = False if lines[i].strip() in _DOC_KEYWORDS: tosub = len(lines[i]) if lines[i].strip() == "Initline": indent = 4 incode = True else: indent = 0 else: tosub = 0 if "#" in lines[i] and ":" in lines[i]: line = lines[i].replace("# ", "**").replace(" :", "** :") elif indent > 0 and tosub == 0 or prompt: line = " " + lines[i].replace(">>> ", "") else: line = lines[i] f.write(line) class ManualPanel_api(ManualPanel): def __init__(self, parent): ManualPanel.__init__(self, parent) self.parse() def parse(self): if BUILD_RST: prepare_api_doc_tree() self.cleanup() self.needToParse = True count = 1 win = self.makePanel("Intro") self.AddPage(win, "Intro") for key in _KEYWORDS_TREE.keys(): count += 1 win = self.makePanel(key) self.AddPage(win, key) for key2 in _KEYWORDS_TREE[key]: count += 1 win = self.makePanel(key2) self.AddPage(win, key2) self.setStyle() win = self.makePanel("Example 1") self.AddPage(win, "Example 1") win = self.makePanel("Example 2") self.AddPage(win, "Example 2") self.getPage("Intro") wx.CallLater(100, self.AdjustSize) def parseOnSearchName(self, keyword): self.cleanup() keyword = keyword.lower() for key in _KEYWORDS_LIST: if keyword in key.lower(): win = self.makePanel(key) self.AddPage(win, key) self.setStyle() self.getPage("Intro") wx.CallAfter(self.AdjustSize) def parseOnSearchPage(self, keyword): self.cleanup() keyword = keyword.lower() for key in _KEYWORDS_LIST: with open(os.path.join(DOC_PATH, key), "r") as f: text = f.read().lower() if keyword in text: win = self.makePanel(key) self.AddPage(win, key) self.setStyle() self.getPage("Intro") wx.CallAfter(self.AdjustSize) def makePanel(self, obj=None): panel = wx.Panel(self, -1) panel.isLoad = False if self.needToParse: if obj == "Intro": if BUILD_RST: create_api_doc_index() panel.win = stc.StyledTextCtrl(panel, -1, size=(600, 480), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.SetUseVerticalScrollBar(False) panel.win.SetText(_INTRO_TEXT) elif "Example" in obj: panel.win = stc.StyledTextCtrl(panel, -1, size=(600, 480), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.SetUseVerticalScrollBar(False) if "1" in obj: panel.win.SetText(_EXAMPLE_1) elif "2" in obj: panel.win.SetText(_EXAMPLE_2) else: var = eval(obj) if isinstance(var, str): panel.win = stc.StyledTextCtrl(panel, -1, size=(600, 480), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.SetUseVerticalScrollBar(False) if "Interface_API" in var: if BUILD_RST: create_interface_api_index() for word in _KEYWORDS_LIST: lines = eval(word).__doc__.splitlines() line = "%s : %s\n" % (word, lines[1].replace('"', '').strip()) var += line var += _COLOUR_TEXT var += _COLOURS else: if BUILD_RST: create_base_module_index() panel.win.SetText(var) else: text = var.__doc__ if BUILD_RST: create_api_doc_page(obj, text) panel.win = stc.StyledTextCtrl(panel, -1, size=(600, 480), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.SetUseVerticalScrollBar(False) panel.win.SetText(text.replace(">>> ", "")) panel.win.SaveFile(CeciliaLib.ensureNFD(os.path.join(DOC_PATH, obj))) return panel def getPage(self, word): if word == self.oldPage: self.fromToolbar = False return page_count = self.GetPageCount() for i in range(page_count): text = self.GetPageText(i) if text == word: self.oldPage = word if not self.fromToolbar: self.sequence = self.sequence[0:self.seq_index + 1] self.sequence.append(i) self.seq_index = len(self.sequence) - 1 self.history_check() self.parent.setTitle(text) self.SetSelection(i) panel = self.GetPage(self.GetSelection()) if not panel.isLoad: panel.isLoad = True panel.win = stc.StyledTextCtrl(panel, -1, size=panel.GetSize(), style=wx.BORDER_SUNKEN) panel.win.SetUseHorizontalScrollBar(False) panel.win.LoadFile(os.path.join(CeciliaLib.ensureNFD(DOC_PATH), word)) panel.win.SetMarginWidth(1, 0) if self.searchKey is not None: words = complete_words_from_str(panel.win.GetText(), self.searchKey) _ed_set_style(panel.win, words) else: if "Example" in word: _ed_set_style_p(panel.win) else: _ed_set_style(panel.win) panel.win.SetSelectionEnd(0) def OnPanelSize(evt, win=panel.win): win.SetPosition((0, 0)) win.SetSize(evt.GetSize()) panel.Bind(wx.EVT_SIZE, OnPanelSize) self.fromToolbar = False return class ManualFrame(wx.Frame): def __init__(self, parent=None, id=-1, size=(950, 650), kind="api"): if kind == "api": title = 'API Documentation' else: title = 'Modules Documentation' wx.Frame.__init__(self, parent=parent, id=id, title=title, size=size) self.SetMinSize((600, 480)) self.kind = kind gosearchID = 1000 aTable = wx.AcceleratorTable([(wx.ACCEL_NORMAL, 47, gosearchID)]) self.SetAcceleratorTable(aTable) self.Bind(wx.EVT_MENU, self.setSearchFocus, id=gosearchID) self.toolbar = self.CreateToolBar() self.toolbar.SetToolBitmapSize((24, 24)) back_ico = CeciliaLib.getVar("ICON_DOC_PREVIOUS") forward_ico = CeciliaLib.getVar("ICON_DOC_NEXT") home_ico = CeciliaLib.getVar("ICON_DOC_UP") backTool = self.toolbar.AddTool(wx.ID_BACKWARD, "", back_ico, "Back") self.toolbar.EnableTool(wx.ID_BACKWARD, False) self.Bind(wx.EVT_MENU, self.onBack, backTool) self.toolbar.AddSeparator() forwardTool = self.toolbar.AddTool(wx.ID_FORWARD, "", forward_ico, "Forward") self.toolbar.EnableTool(wx.ID_FORWARD, False) self.Bind(wx.EVT_MENU, self.onForward, forwardTool) self.toolbar.AddSeparator() homeTool = self.toolbar.AddTool(wx.ID_HOME, "", home_ico, "Go Home") self.toolbar.EnableTool(wx.ID_HOME, True) self.Bind(wx.EVT_MENU, self.onHome, homeTool) self.toolbar.AddSeparator() self.searchTimer = None self.searchScope = "Page Names" self.searchMenu = wx.Menu() item = self.searchMenu.Append(-1, "Search Scope") item.Enable(False) for i, txt in enumerate(["Page Names", "Manual Pages"]): id = i + 10 self.searchMenu.Append(id, txt) self.Bind(wx.EVT_MENU, self.onSearchScope, id=id) self.search = wx.SearchCtrl(self.toolbar, 200, size=(200, -1), style=wx.WANTS_CHARS | wx.TE_PROCESS_ENTER) self.search.ShowCancelButton(True) self.search.SetMenu(self.searchMenu) self.toolbar.AddControl(self.search) self.Bind(wx.EVT_TEXT, self.onSearch, id=200) self.Bind(wx.EVT_TEXT_ENTER, self.onSearchEnter, id=200) self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.onSearchCancel, id=200) self.toolbar.Realize() self.status = wx.StatusBar(self, -1) self.SetStatusBar(self.status) if kind == "api": self.doc_panel = ManualPanel_api(self) self.doc_panel.getPage("Intro") else: self.doc_panel = ManualPanel_modules(self) self.doc_panel.getPage("Modules") self.menuBar = wx.MenuBar() menu1 = wx.Menu() menu1.Append(99, "Close\tCtrl+W") self.menuBar.Append(menu1, 'File') menu2 = wx.Menu() menu2.Append(101, "Copy\tCtrl+C") self.menuBar.Append(menu2, 'Text') self.SetMenuBar(self.menuBar) self.Bind(wx.EVT_MENU, self.copy, id=101) self.Bind(wx.EVT_MENU, self.close, id=99) self.Bind(wx.EVT_CLOSE, self.close) def openPage(self, page): self.doc_panel.getPage(page) if not self.IsShownOnScreen(): self.Show() self.Raise() def setSearchFocus(self, evt): self.search.SetFocus() def onSearchEnter(self, evt): return # TreeBook has no more a GetTreeCtrl method. Don't know how to retrieve it... self.doc_panel.GetTreeCtrl().SetFocus() def onSearch(self, evt): if self.searchTimer is not None: self.searchTimer.Stop() self.searchTimer = wx.CallLater(200, self.doSearch) def doSearch(self): keyword = self.search.GetValue() if keyword == "": self.doc_panel.parse() else: if self.searchScope == "Page Names": self.doc_panel.parseOnSearchName(keyword) else: self.doc_panel.parseOnSearchPage(keyword) self.searchTimer = None def onSearchCancel(self, evt): self.search.SetValue("") def onSearchScope(self, evt): id = evt.GetId() if id == 10: self.searchScope = "Page Names" else: self.searchScope = "Manual Pages" def copy(self, evt): self.doc_panel.copy() def close(self, evt): self.Hide() def setTitle(self, page): if self.kind == "api": self.SetTitle('API Documentation - %s' % page) else: self.SetTitle('Modules Documentation - %s' % page) def history_check(self, back, forward): self.toolbar.EnableTool(wx.ID_BACKWARD, back) self.toolbar.EnableTool(wx.ID_FORWARD, forward) def onBack(self, evt): self.doc_panel.history_back() def onForward(self, evt): self.doc_panel.history_forward() def onHome(self, evt): search = self.search.GetValue() if search != "": self.search.SetValue("") self.doc_panel.getPage("Intro") self.doc_panel.collapseAll()cecilia5-5.4.1/Resources/Drunk.py000077500000000000000000000134701372272363700166320ustar00rootroot00000000000000""" Copyright 2011 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ # Random: # Randomly choose, within a certain range, a new next value # arg 1: maxStepSize (negative value not allowed stepSize == 0) # arg 2: maximum value allowed import random class Drunk: def __init__(self, minValue, maxValue): self.lastValue = random.randint(minValue, maxValue) self.minValue = minValue self.maxValue = maxValue def next(self, maxStepSize=-2): """Method to call a new value from a generator objet. drunk, droneAndJump, repeater and loopseg: maxStepSize = -2 markov: no argument --- PARAMETERS --- maxStepSize : Maximum step size for each call. If negative, repetition are not permitted. """ if self.lastValue < self.minValue or self.lastValue > self.maxValue: return random.randint(self.minValue, self.maxValue) direction = self.getDirection() stepSize = self.getStepSize(direction, abs(maxStepSize)) if maxStepSize < 0: minStepSize = 1 else: minStepSize = 0 self.lastValue += direction * random.randint(minStepSize, stepSize) if self.lastValue < self.minValue: self.lastValue = self.minValue elif self.lastValue > self.maxValue: self.lastValue = self.maxValue else: self.lastValue = self.lastValue return self.lastValue def getDirection(self): if self.lastValue == self.minValue: return 1 elif self.lastValue == self.maxValue: return -1 else: return random.choice([1, -1]) def getStepSize(self, direction, maxStepSize): if direction == -1: return min(maxStepSize, self.lastValue - self.minValue) else: return min(maxStepSize, self.maxValue - self.lastValue) def setLastValue(self, val): self.lastValue = val class DroneAndJump(Drunk): def __init__(self, minValue, maxValue): Drunk.__init__(self, minValue, maxValue) self.minValue = minValue self.maxValue = maxValue self.beforeLastValue = random.randint(minValue, maxValue) self.lastValue = self.beforeLastValue + 1 def next(self, maxStepSize=-2): if self.beforeLastValue != self.lastValue: self.lastValue = self.beforeLastValue return self.beforeLastValue self.beforeLastValue = self.lastValue self.lastValue = Drunk.next(self, abs(maxStepSize)) return self.lastValue def getStepSize(self, direction, maxStepSize): if random.randint(0, 100) < 25: return Drunk.getStepSize(self, direction, maxStepSize) else: return Drunk.getStepSize(self, direction, 0) def setLastValue(self, val): self.beforeLastValue = val self.lastValue = self.beforeLastValue + 1 class Repeater(Drunk): def __init__(self, minValue, maxValue): Drunk.__init__(self, minValue, maxValue) self.minValue = minValue self.maxValue = maxValue self.lastValue = random.randint(minValue, maxValue) def next(self, maxStepSize=-2): self.lastValue = Drunk.next(self, abs(maxStepSize)) return self.lastValue def getStepSize(self, direction, maxStepSize): if random.randint(0, 100) < 20: return Drunk.getStepSize(self, direction, maxStepSize) else: return Drunk.getStepSize(self, direction, 0) class Loopseg(Drunk): def __init__(self, minValue, maxValue): Drunk.__init__(self, minValue, maxValue) self.minValue = minValue self.maxValue = maxValue self.recordedValues = [] self.recordState = 2 self.recordPlayback = 0 self.loopPlayback = 1 self.recordLength = random.randint(3, 6) self.recordLoopTime = random.randint(1, 4) def next(self, maxStepSize=-2): if self.recordState == 2: self.lastValue = Drunk.next(self, maxStepSize) self.recordState = random.choice([2, 2, 2, 1]) if len(self.recordedValues) != self.recordLength and self.recordState == 1: self.lastValue = Drunk.next(self, maxStepSize) self.recordedValues.append(self.lastValue) elif self.recordState == 1 or self.recordState == 0: self.recordState = 0 if self.recordPlayback < self.recordLength: self.loopAround() else: if self.loopPlayback < self.recordLoopTime: self.recordPlayback = 0 self.loopPlayback += 1 self.loopAround() else: self.recordedValues = [] self.recordState = 2 self.recordPlayback = 0 self.loopPlayback = 1 self.recordLength = random.randint(3, 6) self.recordLoopTime = random.randint(1, 4) self.lastValue = Drunk.next(self, maxStepSize) self.recordedValues = [self.lastValue] return self.lastValue def loopAround(self): self.lastValue = self.recordedValues[self.recordPlayback] self.recordPlayback += 1cecilia5-5.4.1/Resources/Grapher.py000077500000000000000000002227111372272363700171370ustar00rootroot00000000000000""" Copyright 2011 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx, random, bisect, math from wx.lib.stattext import GenStaticText from pyo import reducePoints, distanceToSegment, linToCosCurve import Resources.CeciliaPlot as plot import Resources.CeciliaLib as CeciliaLib from .constants import * from .Grapher_parser import * from .Widgets import * import numpy as _Numeric class MyFileDropTarget(wx.FileDropTarget): def __init__(self, window): wx.FileDropTarget.__init__(self) self.window = window def OnDropFiles(self, x, y, filenames): if os.path.isfile(filenames[0]) and os.path.splitext(filenames[0])[1] in [".c5", ".py"]: wx.CallLater(200, self.window.onOpen, filenames[0]) else: pass class Line: def __init__(self, data, yrange, colour, label='', log=False, name='', size=8192, slider=None, suffix='', curved=False): self.data = data self.yrange = yrange self.scale = yrange[1] - yrange[0] self.offset = yrange[0] self.label = label self.log = log self.name = name self.size = size self.slider = slider self.show = 1 self.suffix = suffix self.colour = colour[0] self.midColour = colour[1] self.knobColour = colour[2] self.sliderColour = colour[3] self.curved = False self.lines = [] self.dataToDraw = [] self.initData = self.getLineState() if curved: self.setCurvedLine() self.modified = True def reinit(self): self.setLineState(self.initData) def getModified(self): return self.modified def setModified(self, state): self.modified = state def getLineState(self): dic = {'data': self.normalize(), 'curved': self.getCurved()} return dic def setLineState(self, dic): data = dic['data'][:] self.data = self.denormalize(data) self.curved = dic.get('curved', False) self.checkIfCurved() self.modified = True def changeYrange(self, newrange): d = self.getLineState() self.yrange = newrange self.scale = self.yrange[1] - self.yrange[0] self.offset = self.yrange[0] self.setLineState(d) self.modified = True def getColour(self): return self.colour def getMidColour(self): return self.midColour def getSuffix(self): return self.suffix def getData(self): return self.data[:] def setData(self, list): self.data = list[:] self.checkIfCurved() self.modified = True def reset(self): self.setLineState(self.initData) self.initData = self.getLineState() self.modified = True def setPoint(self, point, value): self.data[point] = value self.checkIfCurved() self.modified = True def move(self, list, offset): self.data = [[l[0] - offset[0], l[1] - offset[1]] for l in list] self.checkIfCurved() self.modified = True def moveLog(self, list, offset): self.data = [[l[0] - offset[0], l[1] * offset[1]] for l in list] self.checkIfCurved() self.modified = True def insert(self, pos, value): self.data.insert(pos, value) self.checkIfCurved() self.modified = True def deletePoint(self, pos): del self.data[pos] self.checkIfCurved() self.modified = True def deletePointFromPoint(self, point): if point in self.data: self.data.remove(point) self.checkIfCurved() self.modified = True def setShow(self, state): self.show = state def getShow(self): return self.show def getYrange(self): return self.yrange def getScale(self): return self.scale def getOffset(self): return self.offset def getLabel(self): return self.label def setName(self, name): self.name = name def getName(self): return self.name def getLog(self): return self.log def getLength(self): return len(self.data) def getSize(self): return self.size def getSlider(self): return self.slider def getCurved(self): return self.curved def getLines(self): return self.lines def normalize(self): data = [p for p in self.getData()] yrange = self.getYrange() totaltime = CeciliaLib.getVar("totalTime") templist = [] if self.getLog(): for l in data: l0 = float(l[0] / totaltime) l1 = float(math.log10(l[1] / yrange[0]) / math.log10(yrange[1] / yrange[0])) templist.append([l0, l1]) else: for l in data: l0 = float(l[0] / totaltime) l1 = float((l[1] - yrange[0]) / (yrange[1] - yrange[0])) templist.append([l0, l1]) return templist def denormalize(self, data): yrange = self.getYrange() totaltime = CeciliaLib.getVar("totalTime") if self.getLog(): for l in data: l[0] = l[0] * totaltime l[1] = math.pow(10, l[1] * (math.log10(yrange[1]) - math.log10(yrange[0])) + math.log10(yrange[0])) else: for l in data: l[0] = l[0] * totaltime l[1] = l[1] * (yrange[1] - yrange[0]) + yrange[0] return data def setCurvedLine(self): if self.curved: self.curved = False else: self.curved = True self.lines = linToCosCurve(data=CeciliaLib.deepCopy(self.getData()), yrange=self.getYrange(), totaldur=CeciliaLib.getVar("totalTime"), points=1024, log=self.getLog()) self.modified = True def checkIfCurved(self): if self.getCurved(): self.curved = False self.setCurvedLine() class Grapher(plot.PlotCanvas): def __init__(self, parent, style=wx.EXPAND): plot.PlotCanvas.__init__(self, parent, style=wx.EXPAND | wx.WANTS_CHARS) drop = MyFileDropTarget(CeciliaLib.getVar("mainFrame")) self.canvas.SetDropTarget(drop) self.parent = parent self.menubarUndo = self.parent.parent.menubar.FindItemById(ID_UNDO) self.menubarRedo = self.parent.parent.menubar.FindItemById(ID_REDO) self._history = [] self._historyPoint = 0 self._tool = 0 self._zoomed = False self.SetUseScientificNotation(False) self.SetEnableTitle(False) self.SetFontSizeAxis(GRAPHER_AXIS_FONT) self.SetFontSizeLegend(GRAPHER_LEGEND_FONT) self.SetBackColour(GRAPHER_BACK_COLOUR) self.SetGridColour(wx.Colour(170, 170, 170)) self.SetEnableGrid(True) self.setLogScale((False, False)) self.totaltime = 1 self.movingCurve = False self.curve = None # edited curve self.point = None # edited point self.selectedPoints = [] self.markSelectionStart = None self.lineOver = None # mouse hover curve self.lineOverGate = True self.selected = 0 # selected curve self._oldSelected = -1 self._graphCreation = True self.data = [] self._currentData = [] self._oldData = [] self.visibleLines = [] self._pencilData = [] self._pencilDir = 0 self._pencilOldPos = None self._background_bitmap = CeciliaLib.getVar("ICON_GRAPHER_BACKGROUND") self.Bind(wx.EVT_CHAR, self.OnKeyDown) self.canvas.Bind(wx.EVT_CHAR, self.OnKeyDown) self.canvas.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.canvas.Bind(wx.EVT_ENTER_WINDOW, self.OnGrabFocus) def unselectPoints(self): self.selectedPoints = [] def OnLooseFocus(self, event): if CeciliaLib.getVar("canGrabFocus"): win = wx.FindWindowAtPointer() if win[0] is not None: win = win[0].GetTopLevelParent() if win not in [CeciliaLib.getVar("mainFrame"), CeciliaLib.getVar("interface"), CeciliaLib.getVar("spectrumFrame")]: win.Raise() event.Skip() def OnGrabFocus(self, event): if CeciliaLib.getVar("canGrabFocus"): self.SetFocus() event.Skip() def setTool(self, tool): self._tool = tool if self._tool < 2: self.SetToolCursor(tool) self.SetEnableZoom(False, tool) self.SetEnableDrag(False, tool) elif self._tool == 2: self.SetEnableZoom(True) else: self.SetEnableDrag(True) def setTotalTime(self, time): oldTotalTime = self.totaltime self.totaltime = time if self.data: self._oldSelected = -1 for line in self.data: line.setData([[self.clipTotalTime(p[0] / oldTotalTime * self.totaltime, self.totaltime), p[1]] for p in line.getData()]) self.draw() def clipTotalTime(self, value, totaltime): if value >= totaltime: return totaltime else: return value def getTotalTime(self): return self.totaltime def getData(self): return self.data def setSelected(self, which): self.selectedPoints = [] if self._zoomed: self.adjustZoomCorners(which) self.selected = which self.draw() def resetSelectedPoints(self): self.selectedPoints = [] self.draw() def getSelected(self): return self.selected def sendSelected(self): self.parent.setSelected(self.selected) def getLine(self, which): return self.data[which] def removeLine(self, name): for line in self.data: if line.getName() == name: self.data.remove(line) break self.draw() def removeLines(self, names): for name in names: for line in self.data: if line.getName() == name: self.data.remove(line) break self.draw() def createLine(self, data, yrange, colour, label='', log=False, name='', size=8192, slider=None, suffix='', curved=False): if data[0][0] != 0: data[0][0] = 0 if data[-1][0] != self.totaltime: data[-1][0] = self.totaltime self.data.append(Line(data, yrange, colour, label, log, name, size, slider, suffix, curved)) # self.draw() def onCopy(self): line = self.getLine(self.getSelected()) data = wx.TextDataObject(str(line.getLineState())) if wx.TheClipboard.Open(): wx.TheClipboard.Clear() wx.TheClipboard.SetData(data) wx.TheClipboard.Close() def onPaste(self): if not wx.TheClipboard.IsOpened(): do = wx.TextDataObject() wx.TheClipboard.Open() success = wx.TheClipboard.GetData(do) wx.TheClipboard.Close() if success: text = do.GetText() else: return state = import_after_effects_automation(text) if state is None: try: state = eval(text) except: return line = self.getLine(self.getSelected()) line.setLineState(state) self.draw() self.onCopy() self.checkForHistory() if line.getSlider() is not None: line.getSlider().setPlay(1) def onSelectAll(self): data = self.getLine(self.getSelected()).getData() self.selectedPoints = [i for i in range(len(data))] self.draw() def checkForHistory(self, fromUndo=False): if self._oldData != self._currentData: self.addHistory(fromUndo) self._oldData = self._currentData def addHistory(self, fromUndo=False): data = {} for i, l in enumerate(self.data): data[i] = l.getLineState() if len(self._history) > 100: del self._history[-1] if len(self._history): if data != self._history[0]: self._history.insert(0, data) else: self._history.insert(0, data) if not fromUndo: self._historyPoint = 0 def undoRedo(self, dir): if dir == 1 and self._historyPoint < (len(self._history) - 1): self._historyPoint += dir for i, l in enumerate(self.data): l.setLineState(self._history[self._historyPoint][i]) self.draw() elif dir == -1 and self._historyPoint > 0: self._historyPoint += dir for i, l in enumerate(self.data): l.setLineState(self._history[self._historyPoint][i]) self.draw() if len(self._history) > 0: if self._historyPoint >= (len(self._history) - 1): self.menubarUndo.Enable(False) else: self.menubarUndo.Enable(True) if self._historyPoint <= 0: self.menubarRedo.Enable(False) else: self.menubarRedo.Enable(True) else: self.menubarUndo.Enable(False) self.menubarRedo.Enable(False) def rescaleLinLin(self, data, yrange, currentYrange): scale = yrange[1] - yrange[0] currentScale = currentYrange[1] - currentYrange[0] offset = yrange[0] currentOffset = currentYrange[0] scaling = currentScale / scale return [[p[0], (p[1] - offset) * scaling + currentOffset] for p in data] def rescaleLogLog(self, data, yrange, currentYrange): list = [] totalRange = math.log10(yrange[1] / yrange[0]) currentTotalRange = math.log10(currentYrange[1] / currentYrange[0]) currentMin = math.log10(currentYrange[0]) for p in data: ratio = math.log10(p[1] / yrange[0]) / totalRange list.append([p[0], math.pow(10, ratio * currentTotalRange + currentMin)]) return list def rescaleLinLog(self, data, yrange, currentYrange): list = [] if yrange[0] == 0: yoffrange = .00001 else: yoffrange = yrange[0] totalRange = yrange[1] - yoffrange currentTotalRange = math.log10(currentYrange[1] / currentYrange[0]) currentMin = math.log10(currentYrange[0]) for p in data: if p[1] == 0: p1 = .00001 else: p1 = p[1] ratio = (p1 - yoffrange) / totalRange list.append([p[0], math.pow(10, ratio * currentTotalRange + currentMin)]) return list def rescaleLogLin(self, data, yrange, currentYrange): outlist = [] totalRange = math.log10(yrange[1] / yrange[0]) currentTotalRange = currentYrange[1] - currentYrange[0] currentMin = currentYrange[0] for p in data[:]: ratio = math.log10(p[1] / yrange[0]) / totalRange outlist.append([p[0], ratio * currentTotalRange + currentMin]) return outlist def adjustZoomCorners(self, new): currentLine = self.getLine(self.getSelected()) newLine = self.getLine(new) if currentLine.getLog() and newLine.getLog(): _zoomCorner1 = _Numeric.array([self._zoomCorner1[0], _Numeric.power(10, self._zoomCorner1[1])]) _zoomCorner2 = _Numeric.array([self._zoomCorner2[0], _Numeric.power(10, self._zoomCorner2[1])]) self._zoomCorner1 = _Numeric.array(self.rescaleLogLog([_zoomCorner1], currentLine.getYrange(), newLine.getYrange()))[0] self._zoomCorner2 = _Numeric.array(self.rescaleLogLog([_zoomCorner2], currentLine.getYrange(), newLine.getYrange()))[0] self._zoomCorner1[1] = _Numeric.log10(self._zoomCorner1[1]) self._zoomCorner2[1] = _Numeric.log10(self._zoomCorner2[1]) elif currentLine.getLog() and not newLine.getLog(): _zoomCorner1 = _Numeric.array([self._zoomCorner1[0], _Numeric.power(10, self._zoomCorner1[1])]) _zoomCorner2 = _Numeric.array([self._zoomCorner2[0], _Numeric.power(10, self._zoomCorner2[1])]) self._zoomCorner1 = _Numeric.array(self.rescaleLogLin([_zoomCorner1], currentLine.getYrange(), newLine.getYrange()))[0] self._zoomCorner2 = _Numeric.array(self.rescaleLogLin([_zoomCorner2], currentLine.getYrange(), newLine.getYrange()))[0] elif not currentLine.getLog() and not newLine.getLog(): self._zoomCorner1 = _Numeric.array(self.rescaleLinLin([self._zoomCorner1], currentLine.getYrange(), newLine.getYrange()))[0] self._zoomCorner2 = _Numeric.array(self.rescaleLinLin([self._zoomCorner2], currentLine.getYrange(), newLine.getYrange()))[0] elif not currentLine.getLog() and newLine.getLog(): self._zoomCorner1 = _Numeric.array(self.rescaleLinLog([self._zoomCorner1], currentLine.getYrange(), newLine.getYrange()))[0] self._zoomCorner2 = _Numeric.array(self.rescaleLinLog([self._zoomCorner2], currentLine.getYrange(), newLine.getYrange()))[0] self._zoomCorner1[1] = _Numeric.log10(self._zoomCorner1[1]) self._zoomCorner2[1] = _Numeric.log10(self._zoomCorner2[1]) def getRawData(self): data = [] for l in self.data: data.append([p for p in l.getData()]) data.append(l.getCurved()) return data def draw(self): wx.CallAfter(self._draw) def _draw(self): if len(self.data) == 0: return lines = [] markers = [] self.visibleLines = [] curve = self.data[self.selected] currentYrange = curve.getYrange() currentLog = curve.getLog() tmpData = self.tmpDataOrderSelEnd() if self._graphCreation: needRedrawNonSelCurves = True elif self.selected != self._oldSelected: self._oldSelected = self.selected needRedrawNonSelCurves = True else: needRedrawNonSelCurves = False for l in tmpData: index = self.data.index(l) if index == self.lineOver: col = 'black' else: col = l.getColour() if l.getShow(): if l.getCurved(): data = l.getLines() else: data = l.getData() if index == self.selected: slider = l.slider if slider is None: widget_type = "graph" else: widget_type = slider.widget_type if widget_type == "slider": if l.getSuffix() == "sampler": widget_type = "sampler" sampler_name = slider.name elif widget_type == "range": if l.getLabel().endswith("min"): which = 0 elif l.getLabel().endswith("max"): which = 1 line = plot.PolyLine(CeciliaLib.deepCopy(data), colour=col, width=2, legend=l.getLabel()) if self.movingCurve: marker = plot.PolyMarker([l.getData()[0], l.getData()[-1]], size=1.1, marker="bmp", fillcolour='black') else: marker = plot.PolyMarker(l.getData(), size=1.1, marker="bmp", fillcolour='black') if CeciliaLib.getVar("currentModule") is not None and l.getModified(): if widget_type == "graph": CeciliaLib.getVar("currentModule")._graphs[l.name].setValue(data) elif widget_type == "range": CeciliaLib.getVar("currentModule")._sliders[l.name].setGraph(which, data) elif widget_type == "sampler": CeciliaLib.getVar("currentModule")._samplers[sampler_name].setGraph(l.name, data) elif widget_type == "slider": CeciliaLib.getVar("currentModule")._sliders[l.name].setGraph(data) elif widget_type == "plugin_knob": CeciliaLib.getVar("audioServer").setPluginGraph(slider.getParentVPos(), slider.getKnobPos(), data) l.setModified(False) else: if needRedrawNonSelCurves: if currentLog: if l.getLog(): dataToDraw = self.rescaleLogLog(data, l.getYrange(), currentYrange) else: dataToDraw = self.rescaleLinLog(data, l.getYrange(), currentYrange) else: if l.getLog(): dataToDraw = self.rescaleLogLin(data, l.getYrange(), currentYrange) else: dataToDraw = self.rescaleLinLin(data, l.getYrange(), currentYrange) l.dataToDraw = dataToDraw else: dataToDraw = l.dataToDraw line = plot.PolyLine(dataToDraw, colour=col, width=1, legend=l.getLabel()) marker = plot.PolyMarker([], size=1, marker="none") if l.getLog(): line.setLogScale((False, True)) marker.setLogScale((False, True)) if currentLog: self.setLogScale((False, True)) else: self.setLogScale((False, False)) lines.append(line) markers.append(marker) if self.selectedPoints and index == self.selected: try: selmarker = plot.PolyMarker([l.getData()[selp] for selp in self.selectedPoints], size=1.5, marker="bmpsel", fillcolour='white') markers.append(selmarker) except: pass self.visibleLines.append(l) lines.extend(markers) gc = plot.PlotGraphics(lines, 'Title', '', '') self.Draw(gc, xAxis=(0, self.totaltime), yAxis=currentYrange) self._currentData = self.getRawData() def OnLeave(self, event): self.curve = None self.point = None self.lineOver = None self.draw() def OnMouseDoubleClick(self, event): if self._tool < 2: if self.lineOver is not None and self.lineOver == self.selected: line = self.data[self.lineOver] line.setCurvedLine() if line.getSlider() is not None: line.getSlider().setPlay(1) else: pos = list(self.GetXY(event)) line = self.data[self.selected] points = [p[0] for p in line.getData()] for i in range(len(points) - 1): if pos[0] > points[i] and pos[0] < points[i + 1]: line.insert(i + 1, pos) self.point = i + 1 if pos[0] > points[-1]: line.insert(len(points), pos) self.point = len(points) elif pos[0] < points[0]: line.insert(0, pos) self.point = 0 if line.getSlider() is not None: line.getSlider().setPlay(1) self.curve = self.selected self.setValuesToDraw(self._getXY(event), pos[0], pos[1]) self.draw() def OnMouseRightDown(self, event): pos = self._getXY(event) curve = self.data[self.selected] ldata = self.GetClosestPointOnCurve(pos, curve.getLabel(), pointScaled=True) # test the distance of the closest point if ldata[5] < 5: l = self.data.index(self.visibleLines[ldata[0]]) line = self.data[l] if line.getCurved(): if ldata[2] == 0 or ldata[2] == (len(line.getLines()) - 1): return if line.getLines()[ldata[2]] in line.getData(): line.deletePoint(line.getData().index(line.getLines()[ldata[2]])) else: if ldata[2] == 0 or ldata[2] == (len(line.getData()) - 1): return line.deletePoint(ldata[2]) self.draw() self.checkForHistory() def pointsNear(self, p1, p2): if (p2[0] - .5) < p1[0] < (p2[0] + .5) and (p2[1] - .5) < p1[1] < (p2[1] + .5): return True else: return False def OnMouseLeftDown(self, event): tmp_selectedPoints = [p for p in self.selectedPoints] if self._tool > 1: self._zoomCorner1[0], self._zoomCorner1[1] = self._getXY(event) self._screenCoordinates = _Numeric.array(event.GetPosition()) if self._dragEnabled: self.SetCursor(self.GrabHandCursor) self.canvas.CaptureMouse() pos = self._getXY(event) curve = self.data[self.selected] ldata = None if curve.getShow(): ldata = self.GetClosestPointOnCurve(pos, curve.getLabel(), pointScaled=True) if ldata: # grab a point and select the line if ldata[5] < 5: l = self.data.index(self.visibleLines[ldata[0]]) p = ldata[2] line = self.data[l] if line.getCurved(): pt = p for point in line.getData(): if self.pointsNear(line.getLines()[pt], point): p = line.getData().index(point) break else: p = None if p is not None and line.getShow(): self.curve = l self.point = p if self._tool == 0: if p not in tmp_selectedPoints: self.selectedPoints = [p] if event.ShiftDown() and tmp_selectedPoints: self.selectedPoints.extend([pt for pt in tmp_selectedPoints if pt not in self.selectedPoints]) self.selectedPoints.sort() self.templist = [[l[0], l[1]] for i, l in enumerate(self.data[self.selected].getData()) if i in self.selectedPoints] self.startpos = self.lastpos = self.GetXY(event) self.selected = self.curve self.sendSelected() self.setValuesToDraw(self._getXY(event), pos[0], pos[1]) self.draw() event.Skip() return self.selectedPoints = [] if self._tool == 1: pos = list(self.GetXY(event)) self._pencilData = [] self.startpos = pos self._pencilOldPos = pos line = self.data[self.selected] points = [p[0] for p in line.getData()] for i in range(len(points) - 1): if pos[0] > points[i] and pos[0] < points[i + 1]: line.insert(i + 1, pos) if pos[0] > points[-1]: line.insert(len(points), pos) elif pos[0] < points[0]: line.insert(0, pos) if line.getSlider() is not None: line.getSlider().setPlay(1) self.curve = self.selected self.point = None self.setValuesToDraw(self._getXY(event), pos[0], pos[1]) self.draw() else: pos = self.GetXY(event) # Shift-click on the selected line add a new point if self.lineOver is not None and self.lineOver == self.selected and event.ShiftDown(): pos = list(self.GetXY(event)) line = self.data[self.selected] Xs = [p[0] for p in line.getData()] Ys = [p[1] for p in line.getData()] for i in range(len(Xs) - 1): if pos[0] > Xs[i] and pos[0] < Xs[i + 1]: distance = (pos[0] - Xs[i]) / (Xs[i + 1] - Xs[i]) y = Ys[i] + (Ys[i + 1] - Ys[i]) * distance line.insert(i + 1, [pos[0], y]) break # move the line if already selected elif self.lineOver is not None and self.lineOver == self.selected: self.startpos = pos self.curve = self.lineOver # set extreme Xs and Ys for clipping self.templist = [[l[0], l[1]] for l in self.data[self.curve].getData()] Xs = [p[0] for p in self.templist] self.extremeXs = (min(Xs), max(Xs)) Ys = [p[1] for p in self.templist] self.extremeYs = (min(Ys), max(Ys)) self.draw() else: if self._tool == 0: self.markSelectionStart = self.GetXY(event) self._markSelectionStart = self._getXY(event) if tmp_selectedPoints != []: tmp_selectedPoints = [] self.draw() event.Skip() def OnMouseLeftUp(self, event): if self._zoomEnabled: if self._hasDragged: self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old self._zoomCorner2[0], self._zoomCorner2[1] = self._getXY(event) tmp_Y = max(self._zoomCorner1[1], self._zoomCorner2[1]) - min(self._zoomCorner2[1], self._zoomCorner1[1]) tmp_X = max(self._zoomCorner1[0], self._zoomCorner2[0]) - min(self._zoomCorner2[0], self._zoomCorner1[0]) # maximum percentage of zooming if tmp_Y >= 0.01 and (tmp_X / self.getTotalTime()) >= 0.01: minX, minY = _Numeric.minimum(self._zoomCorner1, self._zoomCorner2) maxX, maxY = _Numeric.maximum(self._zoomCorner1, self._zoomCorner2) self._hasDragged = False # reset flag self.last_PointLabel = None #reset pointLabel self._zoomed = True if self.last_draw is not None: self._Draw(self.last_draw[0], xAxis=(minX, maxX), yAxis=(minY, maxY), dc=None) else: self._hasDragged = False # reset flag self.last_PointLabel = None #reset pointLabel if self.last_draw is not None: self._Draw(self.last_draw[0], xAxis=self.last_draw[1], yAxis=self.last_draw[2], dc=None) if self._dragEnabled: self.SetCursor(self.HandCursor) if self.canvas.HasCapture(): self.canvas.ReleaseMouse() if self.markSelectionStart is not None: self.selectedPoints = [] markSelectionEnd = self.GetXY(event) X = min(markSelectionEnd[0], self.markSelectionStart[0]) Y = min(markSelectionEnd[1], self.markSelectionStart[1]) W = max(markSelectionEnd[0], self.markSelectionStart[0]) - X H = max(markSelectionEnd[1], self.markSelectionStart[1]) - Y rect = wx.Rect2D(X, Y, W, H) data = self.getLine(self.getSelected()).getData() for p in data: if rect.Contains(p): self.selectedPoints.append(data.index(p)) self.markSelectionStart = None self.drawSelectionRect(None, None) self.draw() self.checkForHistory() self.movingCurve = False self.curve = None self.point = None self.setValuesToDraw(self._getXY(event)) self.draw() def OnMotion(self, event): if self._zoomEnabled and event.LeftIsDown() and self._tool > 1 and self.curve is None: if self._hasDragged: self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old else: self._hasDragged = True self._zoomCorner2[0], self._zoomCorner2[1] = self._getXY(event) self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new elif self._dragEnabled and event.LeftIsDown() and self._zoomed and self._tool > 1 and self.curve is None: coordinates = event.GetPosition() newpos, oldpos = map(_Numeric.array, map(self.PositionScreenToUser, [coordinates, self._screenCoordinates])) dist = newpos - oldpos self._screenCoordinates = coordinates yRange = self.getLine(self.getSelected()).getYrange() if self.last_draw is not None: graphics, xAxis, yAxis = self.last_draw yAxis -= dist[1] xAxis -= dist[0] if xAxis[0] < 0: xAxis[1] = xAxis[1] - xAxis[0] xAxis[0] = 0 elif xAxis[1] > self.getTotalTime(): xAxis[0] = self.getTotalTime() - (xAxis[1] - xAxis[0]) xAxis[1] = self.getTotalTime() if self.getLine(self.getSelected()).getLog(): if _Numeric.power(10, yAxis[0]) < yRange[0]: yAxis[1] = _Numeric.log10(yRange[0]) + yAxis[1] - yAxis[0] yAxis[0] = _Numeric.log10(yRange[0]) elif _Numeric.power(10, yAxis[1]) > yRange[1]: yAxis[0] = _Numeric.log10(yRange[1]) - (yAxis[1] - yAxis[0]) yAxis[1] = _Numeric.log10(yRange[1]) else: if yAxis[0] < yRange[0]: yAxis[1] = yRange[0] + yAxis[1] - yAxis[0] yAxis[0] = yRange[0] elif yAxis[1] > yRange[1]: yAxis[0] = yRange[1] - (yAxis[1] - yAxis[0]) yAxis[1] = yRange[1] self._zoomCorner1[0] = xAxis[0] self._zoomCorner2[0] = xAxis[1] self._zoomCorner1[1] = yAxis[0] self._zoomCorner2[1] = yAxis[1] self._Draw(graphics, xAxis, yAxis) if self._tool == 0: pos = self.GetXY(event) # Moves point if self.point is not None: if not self.selectedPoints: if self.point == 0: minboundary = self.GetXCurrentRange()[0] else: minboundary = self.data[self.curve].getData()[self.point - 1][0] if self.point == (len(self.data[self.curve].getData()) - 1): maxboundary = self.GetXCurrentRange()[1] else: maxboundary = self.data[self.curve].getData()[self.point + 1][0] if pos[0] < minboundary: X = minboundary elif pos[0] > maxboundary: X = maxboundary else: X = pos[0] if pos[1] < self.GetYCurrentRange()[0]: Y = self.GetYCurrentRange()[0] elif pos[1] > self.GetYCurrentRange()[1]: Y = self.GetYCurrentRange()[1] else: Y = pos[1] if self.point == 0: X = 0 if self.point == (self.data[self.curve].getLength() - 1): X = self.totaltime self.data[self.curve].setPoint(self.point, [X, Y]) else: currentYrange = self.data[self.selected].getYrange() selectedPoints = [p for p in self.selectedPoints] if pos[0] > self.lastpos[0]: selectedPoints.reverse() for p in selectedPoints: if p == 0: minboundary = self.GetXCurrentRange()[0] else: minboundary = self.data[self.curve].getData()[p - 1][0] if p == (len(self.data[self.curve].getData()) - 1): maxboundary = self.GetXCurrentRange()[1] else: maxboundary = self.data[self.curve].getData()[p + 1][0] if self.data[self.selected].getLog(): if event.AltDown() and event.ShiftDown(): offset = (self.startpos[0] - pos[0], 1) elif event.AltDown(): offset = (0, pos[1] / self.startpos[1]) else: offset = (self.startpos[0] - pos[0], pos[1] / self.startpos[1]) else: if event.AltDown() and event.ShiftDown(): offset = (self.startpos[0] - pos[0], 0) elif event.AltDown(): offset = (0, self.startpos[1] - pos[1]) else: offset = (self.startpos[0] - pos[0], self.startpos[1] - pos[1]) newxpos = self.templist[self.selectedPoints.index(p)][0] - offset[0] if newxpos < minboundary: X = minboundary elif newxpos > maxboundary: X = maxboundary else: X = newxpos if self.data[self.selected].getLog(): newypos = self.templist[self.selectedPoints.index(p)][1] * offset[1] else: newypos = self.templist[self.selectedPoints.index(p)][1] - offset[1] if newypos < self.GetYCurrentRange()[0]: Y = self.GetYCurrentRange()[0] elif newypos > self.GetYCurrentRange()[1]: Y = self.GetYCurrentRange()[1] else: Y = newypos if p == 0: X = 0 if p == (self.data[self.curve].getLength() - 1): X = self.totaltime self.data[self.curve].setPoint(p, [X, Y]) self.lastpos = pos self.setValuesToDraw(self._getXY(event), pos[0], pos[1]) self.draw() # Move line elif self.curve is not None: self.movingCurve = True if self.data[self.selected].getLog(): offset = (self.startpos[0] - pos[0], pos[1] / self.startpos[1]) clipedOffset = self.clipLog(offset, self.extremeXs, self.extremeYs) self.data[self.curve].moveLog(self.templist, clipedOffset) else: offset = (self.startpos[0] - pos[0], self.startpos[1] - pos[1]) clipedOffset = self.clip(offset, self.extremeXs, self.extremeYs) self.data[self.curve].move(self.templist, clipedOffset) self.draw() # draw selection marquee elif self.markSelectionStart is not None: corner1 = self._markSelectionStart corner2 = self._getXY(event) self.drawSelectionRect(corner1, corner2) self.draw() # Check for mouse over elif len(self.data) > 0: self.lineOver = None if self.selected >= len(self.data) or self.selected < 0: self.selected = 0 curve = self.data[self.selected] # Check mouse over if curved if curve.getCurved(): curvePosCheck = self._getXY(event) ldata = self.GetClosestPointOnCurve(curvePosCheck, curve.getLabel(), pointScaled=True) if ldata[5] < 10: if ldata[0] < len(self.visibleLines): l = self.data.index(self.visibleLines[ldata[0]]) if self.data.index(curve) == l: self.lineOver = self.data.index(curve) self.lineOverGate = True else: # Check mouse over if not curved currentYrange = curve.getYrange() checkPos = (pos[0], pos[1]) curveData = curve.getData() pourcent = 0.005 it = [x[0] for x in curveData] i = bisect.bisect_right(it, checkPos[0]) - 1 if i >= (len(curveData) - 2): i = len(curveData) - 3 if distanceToSegment(checkPos, curveData[i], curveData[i + 1], 0, self.totaltime, currentYrange[0], currentYrange[1], False, curve.getLog()) <= pourcent: self.lineOver = self.data.index(curve) self.lineOverGate = True elif distanceToSegment(checkPos, curveData[i - 1], curveData[i], 0, self.totaltime, currentYrange[0], currentYrange[1], False, curve.getLog()) <= pourcent: self.lineOver = self.data.index(curve) self.lineOverGate = True elif distanceToSegment(checkPos, curveData[i + 1], curveData[i + 2], 0, self.totaltime, currentYrange[0], currentYrange[1], False, curve.getLog()) <= pourcent: self.lineOver = self.data.index(curve) self.lineOverGate = True else: self.lineOver = None if self.lineOverGate: self.draw() if self.lineOver is None: self.lineOverGate = False elif self._tool == 1 and event.LeftIsDown(): pos = self.GetXY(event) if self._pencilOldPos is None: self._pencilOldPos = pos line = self.data[self.selected] if pos[0] < 0.0: pos = [0.0, pos[1]] elif pos[0] > CeciliaLib.getVar("totalTime"): pos = [CeciliaLib.getVar("totalTime"), pos[1]] minY, maxY = line.getYrange()[0], line.getYrange()[1] if pos[1] < minY: pos = [pos[0], minY] elif pos[1] > maxY: pos = [pos[0], maxY] if line.getLog(): distance = self.distanceLog(pos, self._pencilOldPos, line.getYrange()) else: distance = self.distance(pos, self._pencilOldPos, line.getScale()) if distance > 0.001: if self._pencilOldPos[0] < pos[0]: _pencilDir = 0 else: _pencilDir = 1 if _pencilDir != self._pencilDir: self._pencilDir = _pencilDir self._pencilData = [] self.startpos = pos minpos = min(self.startpos[0], pos[0]) maxpos = max(self.startpos[0], pos[0]) for p in line.getData(): if p[0] >= minpos and p[0] <= maxpos and p not in self._pencilData: line.deletePointFromPoint(p) points = [p[0] for p in line.getData()] for i in range(len(points) - 1): if pos[0] > points[i] and pos[0] < points[i + 1]: line.insert(i + 1, pos) self._pencilData.append(pos) if pos[0] >= points[-1]: line.deletePoint(-1) line.data.append([CeciliaLib.getVar("totalTime"), pos[1]]) self._pencilData.append(pos) elif pos[0] <= points[0]: line.deletePoint(0) line.data.insert(0, [0.0, pos[1]]) self._pencilData.append(pos) if line.getSlider() is not None: line.getSlider().setPlay(1) self.setValuesToDraw(self._getXY(event), pos[0], pos[1]) self._pencilOldPos = pos self.point = None self.draw() event.Skip() def OnKeyDown(self, event): key = event.GetKeyCode() if key == 118: self.parent.toolbar.radiotoolbox.setTool('pointer') elif key == 112: self.parent.toolbar.radiotoolbox.setTool('pencil') elif key == 122 and not event.CmdDown(): self.parent.toolbar.radiotoolbox.setTool('zoom') elif key == 104: self.parent.toolbar.radiotoolbox.setTool('hand') elif key in [wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE, wx.WXK_BACK]: if self.selectedPoints: numpts = len(self.data[self.selected].getData()) points = [self.data[self.selected].getData()[p] for p in self.selectedPoints if p not in [0, numpts - 1]] for p in points: self.data[self.selected].deletePointFromPoint(p) self.selectedPoints = [] self.draw() self.checkForHistory() elif key in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_UP, wx.WXK_DOWN]: # ENHANCEMENT: # The idea here is to move the selected points with arrows. pass if self._zoomed and key == wx.WXK_ESCAPE: self._zoomed = False self.draw() event.Skip() def tmpDataOrderSelBegin(self): tmpData = [self.data[self.selected]] tmpData += [curve for curve in self.data if curve != self.data[self.selected]] return tmpData def tmpDataOrderSelEnd(self): tmpData = [curve for curve in self.data if curve != self.data[self.selected]] tmpData.reverse() tmpData += [self.data[self.selected]] return tmpData def clip(self, off, exXs, exYs): x, y = off minX, maxX = 0, self.getTotalTime() minY, maxY = self.getLine(self.getSelected()).getYrange()[0], self.getLine(self.getSelected()).getYrange()[1] if exXs[0] - x >= minX and exXs[1] - x <= maxX: x = x elif exXs[1] - x >= maxX: x = exXs[1] - maxX else: x = exXs[0] if exYs[0] - y >= minY and exYs[1] - y <= maxY: y = y elif exYs[1] - y >= maxY: y = exYs[1] - maxY else: y = exYs[0] - minY return (x, y) def clipLog(self, off, exXs, exYs): x, y = off minX, maxX = 0, self.getTotalTime() minY, maxY = self.getLine(self.getSelected()).getYrange()[0], self.getLine(self.getSelected()).getYrange()[1] if exXs[0] - x >= minX and exXs[1] - x <= maxX: x = x elif exXs[1] - x >= maxX: x = exXs[1] - maxX else: x = exXs[0] if exYs[0] * y >= minY and exYs[1] * y <= maxY: y = y elif exYs[1] * y >= maxY: y = maxY / exYs[1] else: y = minY / exYs[0] return (x, y) def distance(self, p1, p2, yscale): "Length of line between two points" xscl = 1. / self.totaltime yscl = 1. / yscale return math.sqrt(pow((p2[0] - p1[0]) * xscl, 2) + pow((p2[1] - p1[1]) * yscl, 2)) def distanceLog(self, p1, p2, yrange): "Length of line between two points (based on X scale and Y ratio)" xscl = 1. / self.totaltime Y = math.log10(p2[1] / p1[1]) / math.log10(yrange[1] / yrange[0]) return math.sqrt(pow((p2[0] - p1[0]) * xscl, 2) + pow(Y, 2)) class ToolBar(wx.Panel): def __init__(self, parent, size=(-1, 30), tools=[], toolFunctions=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetBackgroundColour(TITLE_BACK_COLOUR) self.box = wx.FlexGridSizer(1, 7, 5, 5) self.parent = parent ffakePanel = wx.Panel(self, -1, size=(5, self.GetSize()[1])) ffakePanel.SetBackgroundColour(TITLE_BACK_COLOUR) self.menu = CustomMenu(self, choice=[], size=(150, 20), init=None, outFunction=self.parent.onPopupMenu) self.menu.setBackgroundColour(TITLE_BACK_COLOUR) CeciliaLib.setToolTip(self.menu, TT_GRAPH_POPUP) self.toolbox = ToolBox(self, tools=tools, outFunction=toolFunctions) CeciliaLib.setToolTip(self.toolbox, TT_GRAPHER_TOOLS) self.toolbox.setBackColour(TITLE_BACK_COLOUR) self.convertSlider = ConvertSlider(self, self.GetParent()) self.convertSlider.setBackColour(TITLE_BACK_COLOUR) CeciliaLib.setToolTip(self.convertSlider, TT_RES_SLIDER) tw, th = self.GetTextExtent("loading buffers... ") self.fakePanel = wx.Panel(self, -1, size=(tw, self.GetSize()[1])) self.fakePanel.SetBackgroundColour(TITLE_BACK_COLOUR) if CeciliaLib.getVar("systemPlatform") == "win32": self.loadingMsg = GenStaticText(self.fakePanel, -1, label="loading buffers... ", pos=(-1, 7)) else: self.loadingMsg = wx.StaticText(self.fakePanel, -1, label="loading buffers... ", pos=(-1, 7)) self.loadingMsg.SetBackgroundColour(TITLE_BACK_COLOUR) self.loadingMsg.SetForegroundColour(TITLE_BACK_COLOUR) font = self.loadingMsg.GetFont() font.SetFamily(wx.FONTFAMILY_SWISS) font.SetPointSize(MENU_FONT) self.loadingMsg.SetFont(font) self.loadingMsg.Refresh() self.radiotoolbox = RadioToolBox(self, outFunction=[self.toolPointer, self.toolPencil, self.toolZoom, self.toolHand]) CeciliaLib.setToolTip(self.radiotoolbox, TT_GRAPHER_POINTERS) self.palettetoolbox = PaletteToolBox(self) CeciliaLib.setToolTip(self.palettetoolbox, TT_GRAPHER_GENERATORS) self.box.Add(ffakePanel, 0) self.box.Add(self.menu, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) self.box.Add(self.toolbox, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) self.box.Add(self.convertSlider, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) self.box.Add(self.fakePanel, 1, wx.EXPAND | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 20) self.box.Add(self.radiotoolbox, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 20) self.box.Add(self.palettetoolbox, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 20) self.Bind(wx.EVT_CHAR, self.OnKeyDown) self.box.AddGrowableCol(3) self.SetSizerAndFit(self.box) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.fakePanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) def OnLooseFocus(self, event): win = wx.FindWindowAtPointer() if win[0] is not None: win = win[0].GetTopLevelParent() if win not in [CeciliaLib.getVar("mainFrame"), CeciliaLib.getVar("interface"), CeciliaLib.getVar("spectrumFrame")]: win.Raise() event.Skip() def setPopupChoice(self, choice): self.menu.setChoice(choice) def getPopupChoice(self): return self.menu.getChoice() def toolPointer(self): self.GetParent().plotter.setTool(0) def toolPencil(self): self.GetParent().plotter.setTool(1) def toolZoom(self): self.GetParent().plotter.setTool(2) def toolHand(self): self.GetParent().plotter.setTool(3) def OnKeyDown(self, event): key = event.GetKeyCode() if key == 118: self.radiotoolbox.setTool('pointer') elif key == 112: self.radiotoolbox.setTool('pencil') elif key == 122: self.radiotoolbox.setTool('zoom') elif key == 104: self.radiotoolbox.setTool('hand') self.parent.plotter.OnKeyDown(event) class CursorPanel(wx.Panel): def __init__(self, parent, id=wx.ID_ANY, pos=(20, 20), size=(410, 10)): wx.Panel.__init__(self, parent, id=id, pos=pos, size=size) self.parent = parent self.SetMinSize((100, 10)) self.SetBackgroundColour(BACKGROUND_COLOUR) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.time = 0 self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.bitmap = self.createBitmap() def createBitmap(self): w, h = 10, 10 maskColour = GRAPHER_BACK_COLOUR b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(maskColour)) dc.SetPen(wx.Pen(maskColour)) dc.Clear() rec = wx.Rect(0, 0, w, h) dc.DrawRectangle(rec) gc.SetBrush(wx.Brush(GRADIENT_DARK_COLOUR, wx.SOLID)) gc.SetPen(wx.Pen(GRADIENT_DARK_COLOUR, 1, wx.SOLID)) tri = [(4, h - 1), (0, h - 7), (8, h - 7), (4, h - 1)] gc.DrawLines(tri) dc.SelectObject(wx.NullBitmap) b.SetMaskColour(maskColour) return b def OnLooseFocus(self, event): win = wx.FindWindowAtPointer() if win[0] is not None: win = win[0].GetTopLevelParent() if win not in [CeciliaLib.getVar("mainFrame"), CeciliaLib.getVar("interface"), CeciliaLib.getVar("spectrumFrame")]: win.Raise() event.Skip() def OnLeftDown(self, evt): if not CeciliaLib.getVar("audioServer").isAudioServerRunning(): gap = int(self.parent.plotter.PositionUserToScreen((0, 0))[0]) w, h = self.GetSize() pos = evt.GetPosition()[0] t = float(pos - gap - 4) / (w - gap * 2) * CeciliaLib.getVar("totalTime") if t < 0.0: t = 0.0 CeciliaLib.setVar('startOffset', t) self.setTime(t) def OnPaint(self, evt): if self.parent: gap = int(self.parent.plotter.PositionUserToScreen((0, 0))[0]) totalTime = CeciliaLib.getVar("totalTime") w, h = self.GetSize() curtime = int(self.time / totalTime * (w - gap * 2)) + gap + 4 if curtime > (w - gap): curtime = w - gap dc = wx.AutoBufferedPaintDC(self) dc.SetPen(wx.Pen(GRAPHER_BACK_COLOUR)) dc.SetBrush(wx.Brush(GRAPHER_BACK_COLOUR)) dc.DrawRectangle(0, 0, w, h) dc.DrawBitmap(self.bitmap, curtime - 4, 0) def setTime(self, time): self.time = time wx.CallAfter(self.Refresh) class CECGrapher(wx.Panel): def __init__(self, parent, id=wx.ID_ANY, pos=(20, 20), size=(100, 100)): wx.Panel.__init__(self, parent, id=id, pos=pos, size=size, style=wx.BORDER_SIMPLE) self.parent = parent self.SetMinSize((100, 100)) self.SetBackgroundColour(BACKGROUND_COLOUR) self.showLineState = {} self.mainBox = wx.FlexGridSizer(4, 1, 0, 0) self.toolbar = ToolBar(self, tools=['save', 'load', 'reset', 'show'], toolFunctions=[self.OnSave, self.OnLoad, self.onReset, self.onShow]) self.mainBox.Add(self.toolbar, 0, wx.EXPAND) sepLine = Separator(self, size=(200, 2), colour=BORDER_COLOUR) self.mainBox.Add(sepLine, 0, wx.EXPAND) self.cursorPanel = CursorPanel(self, size=(-1, 10)) self.mainBox.Add(self.cursorPanel, 0, wx.EXPAND) self.plotter = Grapher(self) self.plotter.SetMinSize((100, 100)) self.mainBox.Add(self.plotter, 1, wx.EXPAND | wx.ALL) self.mainBox.AddGrowableCol(0) self.mainBox.AddGrowableRow(3) self.SetSizerAndFit(self.mainBox) self.SetSize(self.GetBestSize()) def getPlotter(self): return self.plotter def setShowLineState(self): if self.showLineState == {}: for line in self.plotter.getData(): self.showLineState[line.getLabel()] = line.getShow() def setShowLineSolo(self, label): self.setShowLineState() for line in self.plotter.getData(): if line.getLabel() == label: line.setShow(True) else: line.setShow(False) self.plotter.draw() def resetShow(self): if self.showLineState != {}: for line in self.plotter.getData(): line.setShow(self.showLineState[line.getLabel()]) self.showLineState = {} self.plotter.draw() def setTotalTime(self, time): self.plotter.setTotalTime(time) def setSamplerLineStates(self, name, state): names = ['%s %s' % (name, n) for n in ['Loop In', 'Loop Time', 'Loop X', 'Gain', 'Transpo']] labels = self.toolbar.getPopupChoice() if state: for n in names: if n not in labels: labels.append(n) else: for n in names: if n in labels: labels.remove(n) self.toolbar.setPopupChoice(labels) names = ['%s%s' % (name, n) for n in ['start', 'end', 'xfade', 'gain', 'trans']] if not state: for line in self.plotter.getData(): if line.getName() in names: line.setShow(False) self.plotter.draw() def createLines(self, list): for l in list: self.createLine(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7]) self.plotter.draw() def createLine(self, points, yrange, colour, label, log, name, size, curved): self.plotter.createLine(points, yrange, colour, label, log, name, size, curved=curved) def createSliderLines(self, list): for l in list: self.createSliderLine(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8]) self.plotter.draw() def createSliderLine(self, points, yrange, colour, label, log, name, size, sl, suffix): self.plotter.createLine(points, yrange, colour, label, log, name, size, sl, suffix) def OnSave(self): line = self.plotter.getLine(self.plotter.getSelected()) dlg = wx.FileDialog(self, message="Save file as ...", defaultDir=CeciliaLib.ensureNFD(os.getcwd()), defaultFile="", style=wx.FD_SAVE) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() f = open(path, 'w') f.write(str(line.getLineState())) f.close() dlg.Destroy() def OnLoad(self): line = self.plotter.getLine(self.plotter.getSelected()) dlg = wx.FileDialog(self, message="Choose a grapher file", defaultDir=CeciliaLib.ensureNFD(CeciliaLib.getVar("grapherLinePath")), defaultFile="", style=wx.FD_OPEN | wx.FD_CHANGE_DIR) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() CeciliaLib.setVar("grapherLinePath", os.path.split(path)[0]) try: f = open(path, 'r') text = f.read() f.close() except: return state = import_after_effects_automation(text) if state is None: try: state = eval(text) except: return line.setLineState(state) self.plotter.draw() self.plotter.checkForHistory() if line.getSlider() is not None: line.getSlider().setPlay(1) dlg.Destroy() def onReset(self): self.plotter.unselectPoints() self.getSelected().reset() if self.getSelected().getSlider() is not None: data = self.getSelected().getData() if len(data) == 2 and data[0][1] == data[1][1]: self.getSelected().getSlider().setPlay(0) self.plotter.draw() def onShow(self, state): self.getSelected().setShow(state) self.plotter.draw() def onPopupMenu(self, ind, val): self.plotter.setSelected(ind) colour = self.getSelected().getMidColour() self.toolbar.menu.setBackColour(colour) self.getSelected().setShow(1) self.toolbar.toolbox.setShow(True) self.plotter.draw() self.checkForConvertSlider() def checkForConvertSlider(self): if self.getSelected().getSlider(): if self.getSelected().getSlider().automationData != []: if self.getSelected().getSlider().widget_type in ["slider", "plugin_knob"]: self.toolbar.convertSlider.initValue(self.getSelected().getSlider().convertSliderValue) elif self.getSelected().getSlider().widget_type == "range": end = self.plotter.getData()[self.plotter.getSelected()].getLabel()[-3:] self.toolbar.convertSlider.initValue(self.getSelected().getSlider().convertSliderValue[end]) self.toolbar.convertSlider.Show() else: self.toolbar.convertSlider.Hide() else: self.toolbar.convertSlider.Hide() def setSelected(self, which): self.toolbar.menu.setByIndex(which) colour = self.plotter.getLine(which).getMidColour() self.toolbar.menu.setBackColour(colour) self.checkForConvertSlider() def getSelected(self): return self.plotter.getLine(self.plotter.getSelected()) def checkForAutomation(self): threshold = .002 self.toolbar.convertSlider.initValue(200) if CeciliaLib.getVar("samplerSliders"): for slider in CeciliaLib.getVar("samplerSliders"): if slider.getRec(): slider.setAutomationLength(CeciliaLib.getControlPanel().getNonZeroTime()) path = slider.getPath() data = convert(path + "_000", slider, threshold, which=None) for line in self.plotter.getData(): if line.getName() == slider.getCName(): self.setLineData(line, data) line.setShow(1) ind = self.plotter.getData().index(line) self.plotter.setSelected(ind) self.setSelected(ind) slider.setRec(0) slider.setPlay(1) if CeciliaLib.getVar("userSliders"): for slider in CeciliaLib.getVar("userSliders"): if slider.getRec(): slider.setAutomationLength(CeciliaLib.getControlPanel().getNonZeroTime()) path = slider.getPath() if type(slider.getValue()) not in [list, tuple]: data = convert(path + "_000", slider, threshold, which=None) for line in self.plotter.getData(): if line.getName() == slider.getName(): self.setLineData(line, data) ind = self.plotter.getData().index(line) self.plotter.setSelected(ind) self.setSelected(ind) slider.setRec(0) slider.setPlay(1) else: for i in range(2): ends = ['min', 'max'] data = convert(path + "_00%d" % i, slider, threshold, which=i) for line in self.plotter.getData(): if line.getName() == slider.getName() and line.getLabel().endswith(ends[i]): self.setLineData(line, data) ind = self.plotter.getData().index(line) self.plotter.setSelected(ind) self.setSelected(ind) slider.setRec(0) slider.setPlay(1) plugins = CeciliaLib.getVar("plugins") for plugin in plugins: if plugin is not None: knobs = plugin.getKnobs() for slider in knobs: if slider.getPath() and slider.getRec(): slider.setAutomationLength(CeciliaLib.getControlPanel().getNonZeroTime()) path = slider.getPath() data = convert(path + "_000", slider, threshold) for line in self.plotter.getData(): if line.getName() == slider.getName(): self.setLineData(line, data) line.setShow(1) ind = self.plotter.getData().index(line) self.plotter.setSelected(ind) self.setSelected(ind) slider.setRec(0) slider.setPlay(1) def setLineData(self, line, data): yrange = line.getYrange() totaltime = self.plotter.getTotalTime() if line.getLog(): for l in data: l[0] = l[0] * totaltime l[1] = math.pow(10, l[1] * (math.log10(yrange[1]) - math.log10(yrange[0])) + math.log10(yrange[0])) else: for l in data: l[0] = l[0] * totaltime l[1] = l[1] * (yrange[1] - yrange[0]) + yrange[0] line.setData(data) self.plotter.draw() self.plotter.checkForHistory() class ConvertSlider(PlainSlider): def __init__(self, parent, cecGrapher): PlainSlider.__init__(self, parent, 50, 2500, 200, log=True, outFunction=self.onSlider1) self.cecGrapher = cecGrapher self.threshold = .01 self.sliderValue = 200 def initValue(self, x): self.SetValue(x) def rescale(self): ends = ['min', 'max'] if self.HasCapture(): line = self.cecGrapher.getSelected() slider = line.getSlider() path = slider.getPath() if type(slider.getValue()) in [list, tuple]: for i in range(2): if line.getLabel().endswith(ends[i]): slider.setConvertSliderValue(self.sliderValue, ends[i]) path = path + "_00%d" % i break data = convert(path, slider, self.thresh, True, which=i) else: slider.setConvertSliderValue(self.sliderValue) data = convert(path, slider, self.thresh, True) self.cecGrapher.setLineData(line, data) def onSlider1(self, value): self.sliderValue = value val = value * .001 self.thresh = self.threshold * val self.rescale() def checkFunctionValidity(func, totaltime): for i, p in enumerate(func): func[i] = (p[0] * totaltime, float(p[1])) if func[0][0] != 0: func[0] = (0, func[0][1]) if func[-1][0] != totaltime: func[-1] = (totaltime, func[-1][1]) oldX = -1 for f in func: if f[0] == oldX: f[0] += 1 oldX = f[0] elif f[0] < oldX: CeciliaLib.showErrorDialog("Error in graph function.", "Time values must be in increasing order!") else: oldX = f[0] return func def checkColourValidity(col): if col not in COLOUR_CLASSES.keys(): CeciliaLib.showErrorDialog('Wrong colour!', '"%s"\n\nAvailable colours for -col flag are:\n\n%s.' % (col, ', '.join(COLOUR_CLASSES.keys()))) col = random.choice(list(COLOUR_CLASSES.keys())) return col def checkLogValidity(linlog, mini, maxi, verbose=False): if linlog not in ['lin', 'log']: if verbose: CeciliaLib.showErrorDialog('Error when building interface!', "'rel' argument choices are 'lin' or 'log'. Reset to 'lin'.") linlog = 'lin' log = {'lin': False, 'log': True}[linlog] if log and mini == 0 or log and maxi == 0: if verbose: CeciliaLib.showErrorDialog('Error when building interface!', "'min' or 'max' arguments can't be 0 for a logarithmic cgraph. Reset to 'lin'.") log = False return log def getGrapher(parent): return CECGrapher(parent) def buildGrapher(grapher): totaltime = CeciliaLib.getVar("totalTime") wlist = CeciliaLib.getVar("interfaceWidgets") grapher.setTotalTime(totaltime) widgetlist = [] widgetlist2 = [] widgetlist2range = [] widgetlist3 = [] labelList = [] for widget in wlist: if widget['type'] == 'cgraph': widgetlist.append(widget) elif widget['type'] == 'cslider': if not widget['up']: widgetlist2.append(CeciliaLib.deepCopy(widget)) elif widget['type'] == 'crange': if not widget['up']: widgetlist2range.append(widget) for widget in CeciliaLib.getVar("samplerSliders"): widgetlist3.append(widget) linelist = [] for i, widget in enumerate(widgetlist): name = widget['name'] label = widget['label'] size = widget['size'] mini = widget['min'] maxi = widget['max'] curved = widget['curved'] func = widget['func'] func = checkFunctionValidity(func, totaltime) linlog = widget['rel'] log = checkLogValidity(linlog, mini, maxi, True) col = widget['col'] col = checkColourValidity(col) colour = CeciliaLib.chooseColourFromName(col) labelList.append(label) linelist.append([func, (mini, maxi), colour, label, log, name, size, curved]) if linelist: grapher.createLines(linelist) linelist = [] for i, widget in enumerate(widgetlist2): name = widget['name'] mini = widget['min'] maxi = widget['max'] init = widget['init'] label = widget['label'] up = widget.get('up', False) func = widget['func'] if func is None: func = [(0, init), (1, init)] init_play = False else: init_play = True func = checkFunctionValidity(func, totaltime) col = widget['col'] col = checkColourValidity(col) colour = CeciliaLib.chooseColourFromName(col) linlog = widget['rel'] log = checkLogValidity(linlog, mini, maxi) for slider in CeciliaLib.getVar("userSliders"): if slider.getName() == name: slider.setFillColour(colour[1], colour[2], colour[3]) sl = slider if init_play: slider.setPlay(1) break labelList.append(label) linelist.append([func, (mini, maxi), colour, label, log, name, 8192, sl, '']) if linelist: grapher.createSliderLines(linelist) linelist = [] ends = ['min', 'max'] for i, widget in enumerate(widgetlist2range): for j in range(2): name = widget['name'] mini = widget['min'] maxi = widget['max'] init = widget['init'][j] label = widget['label'] + ' %s' % ends[j] func = widget['func'][j] if func is None: func = [(0, init), (1, init)] init_play = False else: init_play = True func = checkFunctionValidity(func, totaltime) up = widget.get('up', False) col = widget.get('col', '') col = checkColourValidity(col) if up: colour = CeciliaLib.chooseColourFromName("grey") else: colour = CeciliaLib.chooseColourFromName(col) linlog = widget['rel'] log = checkLogValidity(linlog, mini, maxi) for slider in CeciliaLib.getVar("userSliders"): if slider.getName() == name: slider.setFillColour(colour[1], colour[2], colour[3]) sl = slider if init_play: slider.setPlay(1) break labelList.append(label) linelist.append([func, (mini, maxi), colour, label, log, name, 8192, sl, ends[j]]) if linelist: grapher.createSliderLines(linelist) linelist = [] samplerSliderNames = [] for i, widget in enumerate(widgetlist3): colour = CeciliaLib.chooseColour(5, 5) mini = widget.slider.getRange()[0] maxi = widget.slider.getRange()[1] init = widget.slider.getInit() func = [(0, init), (1, init)] func = checkFunctionValidity(func, totaltime) label = widget.getLabel() log = False name = widget.getCName() for slider in CeciliaLib.getVar("samplerSliders"): samplerSliderNames.append(slider.getCName()) if slider.getCName() == name: sl = slider break labelList.append(label) linelist.append([func, (mini, maxi), colour, label, log, name, 8192, sl, 'sampler']) if linelist: grapher.createSliderLines(linelist) if len(grapher.plotter.getData()) == 0: grapher.createLine([[0, 0], [totaltime, 0]], (0, 1), "#FFFFFF", 'unused', False, 'unused', 8192) labelList.append('unused') for line in grapher.plotter.getData(): if line.getName() in samplerSliderNames: line.setShow(0) checkLineShow = [line.getShow() for line in grapher.plotter.getData()] if 1 not in checkLineShow: grapher.plotter.getData()[0].setShow(1) grapher.toolbar.setPopupChoice(labelList) grapher.plotter.drawCursor(0) grapher.plotter._graphCreation = False def convert(path, slider, threshold, fromSlider=False, which=None): if not fromSlider: reclen = slider.getAutomationLength() f = open(path, 'r') data = f.read().split('\n') data = [x.split() for x in data if x != ''] data = [float(x[1]) for x in data if float(x[0]) <= reclen] f.close() if which is not None: slider.setAutomationData(data, which) else: slider.setAutomationData(data) if which is not None: temp = slider.getAutomationData(which) else: temp = slider.getAutomationData() points = reducePoints(temp, threshold) return points cecilia5-5.4.1/Resources/Grapher_parser.py000066400000000000000000000046021372272363700205050ustar00rootroot00000000000000""" Copyright 2011 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ def import_after_effects_automation(text): lines = text.splitlines() if 'Adobe After Effects' not in lines[0]: return None inside_data = False need_to_check_minmax = False framesPerSecond = 29.97 mini = 0 maxi = 1 data = [] for line in lines[1:]: if 'Units Per Second' in line: elements = line.split("\t") index = elements.index('Units Per Second') framesPerSecond = float(elements[index + 1]) break for i, line in enumerate(lines[1:]): if line.strip() == "": continue else: elements = line.split("\t") while "" in elements: elements.remove("") if "Frame" in elements: inside_data = True if len(elements) == 1: need_to_check_minmax = True elif "percent" in elements: mini, maxi = 0, 100 elif "degrees" in elements: mini, maxi = -360, 360 else: need_to_check_minmax = True elif "End of Keyframe Data" in elements: inside_data = False elif inside_data: data.append([float(elements[0]) / framesPerSecond, float(elements[1])]) if need_to_check_minmax: mini, maxi = min([x[1] for x in data]), max([x[1] for x in data]) data = [[x[0] / data[-1][0], x[1]] for x in data] data = [[x[0], (x[1] - mini) / (maxi - mini)] for x in data] if data[0][0] != 0.0: tmp = [0.0, data[0][1]] data.insert(0, tmp) data = {'curved': False, 'data': data} return data cecilia5-5.4.1/Resources/Plugins.py000066400000000000000000001443041372272363700171660ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx, math import Resources.CeciliaLib as CeciliaLib from .constants import * from .Widgets import * class PluginArrow(wx.Panel): def __init__(self, parent, dir="up", size=(8, 10), outFunction=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.outFunction = outFunction self.dir = dir self.hover = 0 if colour: self.colour = colour else: self.colour = BACKGROUND_COLOUR if self.dir == "up": self.bitmaps = [CeciliaLib.getVar("ICON_PLUGINS_ARROW_UP"), CeciliaLib.getVar("ICON_PLUGINS_ARROW_UP_HOVER")] else: self.bitmaps = [CeciliaLib.getVar("ICON_PLUGINS_ARROW_DOWN"), CeciliaLib.getVar("ICON_PLUGINS_ARROW_DOWN_HOVER")] self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_ENTER_WINDOW, self.MouseEnter) self.Bind(wx.EVT_LEAVE_WINDOW, self.MouseLeave) def MouseEnter(self, evt): self.hover = 1 wx.CallAfter(self.Refresh) def MouseLeave(self, evt): self.hover = 0 wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) dc.DrawBitmap(self.bitmaps[self.hover], 0, 0, True) def MouseDown(self, event): if self.outFunction: self.outFunction(self.dir) #wx.CallAfter(self.Refresh) event.Skip() class PluginKnob(ControlKnob): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(50, 70), log=False, outFunction=None, integer=False, backColour=None, label=''): ControlKnob.__init__(self, parent, minvalue, maxvalue, init, pos, size, log, outFunction, integer, backColour, label) self.Bind(wx.EVT_RIGHT_DOWN, self.MouseRightDown) self.widget_type = "plugin_knob" self.name = '' self.longLabel = '' self.gliss = 0 self.midictl = None self.midichan = 1 self.midictlLabel = '' self.midiLearn = False self.automationLength = None self.automationData = [] self.path = None self.play = False self.rec = False self.convertSliderValue = 200 def getParentVPos(self): return self.GetParent().vpos def getKnobPos(self): names = self.GetParent().getKnobNames() return names.index(self.name) def setConvertSliderValue(self, x, end=None): self.convertSliderValue = x def getValue(self): return self.GetValue() def setValue(self, x): self.SetValue(x) def getName(self): return self.name def setName(self, name): self.name = name self.path = os.path.join(AUTOMATION_SAVE_PATH, self.name) def setLongLabel(self, label): self.longLabel = label def getLongLabel(self): return self.longLabel def setPlay(self, x): if x: self.mode = 2 data = CeciliaLib.getVar("grapher").plotter.data for line in data: if line.getName() == self.name: line.setShow(1) CeciliaLib.getVar("grapher").plotter.draw() else: self.mode = 0 self.Refresh() def setRec(self, x): if x: self.mode = 1 else: self.mode = 0 self.Refresh() def getPlay(self): if self.mode == 2: return True else: return False def getPlayState(self): return self.mode def getRec(self): if self.mode == 1: return True else: return False def getPath(self): return self.path def getState(self): return [self.getValue(), self.getPlayState(), self.getMidiCtl(), self.getMidiChannel()] def setState(self, values): self.setValue(values[0]) self.setPlay(values[1]) self.setMidiCtl(values[2]) if len(values) >= 4: self.setMidiChannel(values[3]) def inMidiLearnMode(self): self.midiLearn = True self.Refresh() def setMidiCtl(self, ctl): if ctl is None: self.midictl = None self.midichan = 1 self.midictlLabel = '' self.midiLearn = False else: self.midictl = int(ctl) self.midictlLabel = str(self.midictl) self.midiLearn = False self.Refresh() def getMidiCtl(self): return self.midictl def setMidiChannel(self, chan): self.midichan = int(chan) def getMidiChannel(self): return self.midichan def getWithMidi(self): if self.getMidiCtl() is not None and CeciliaLib.getVar("useMidi"): return True else: return False def setAutomationLength(self, x): self.automationLength = x def getAutomationLength(self): return self.automationLength def setAutomationData(self, data): # convert values on scaling temp = [] log = self.getLog() minval = self.getMinValue() maxval = self.getMaxValue() automationlength = self.getAutomationLength() frac = automationlength / CeciliaLib.getVar("totalTime") virtuallength = len(data) / frac data.extend([data[-1]] * int(((1 - frac) * virtuallength))) totallength = float(len(data)) oldpos = 0 oldval = data[0] if log: maxOnMin = maxval / minval torec = math.log10(oldval / minval) / math.log10(maxOnMin) else: maxMinusMin = maxval - minval torec = (oldval - minval) / maxMinusMin temp.append([0.0, torec]) for i, val in enumerate(data): length = (i - oldpos) / totallength pos = oldpos / totallength + length if log: torec = math.log10(val / minval) / math.log10(maxOnMin) else: torec = (val - minval) / maxMinusMin temp.append([pos, torec]) oldval = val oldpos = i self.automationData = temp def getAutomationData(self): return [[x[0], x[1]] for x in self.automationData] def update(self, val): if not self.HasCapture() and self.getPlay() == 1 or self.getWithMidi(): self.setValue(val) def MouseRightDown(self, evt): if self._enable: rec = wx.Rect(5, 13, 45, 45) pos = evt.GetPosition() if rec.Contains(pos): if evt.ShiftDown(): self.setMidiCtl(None) else: if CeciliaLib.getVar("useMidi"): CeciliaLib.getVar("audioServer").midiLearn(self) self.inMidiLearnMode() else: CeciliaLib.showErrorDialog("Midi not initialized!", "There is no Midi interface connected!") evt.Skip() class Plugin(wx.Panel): def __init__(self, parent, choiceFunc, order): wx.Panel.__init__(self, parent, pos=wx.DefaultPosition) self.SetBackgroundColour(BACKGROUND_COLOUR) self.choiceFunc = choiceFunc self.order = order self.vpos = order def setKnobLabels(self): if self.pluginName != 'None': for i, knob in enumerate(self.getKnobs()): knob.setLongLabel("PP%d %s %s" % (self.vpos + 1, self.pluginName, knob.getLabel())) def setKnobNames(self): if self.pluginName != 'None': for i, knob in enumerate(self.getKnobs()): knob.setName(self.knobNameTemplates[i] % self.vpos) def replacePlugin(self, i, new): wx.CallLater(50, self.choiceFunc, self.vpos, new) def getName(self): return self.pluginName def getKnobs(self): return [self.knob1, self.knob2, self.knob3] def getKnobNames(self): return [knob.getName() for knob in self.getKnobs()] def getKnobLongLabels(self): return [knob.getLongLabel() for knob in self.getKnobs()] def getParams(self): return [self.knob1.GetValue(), self.knob2.GetValue(), self.knob3.GetValue(), self.preset.getIndex()] def getStates(self): return [self.knob1.getState(), self.knob2.getState(), self.knob3.getState()] def setStates(self, states): self.knob1.setState(states[0]) self.knob2.setState(states[1]) self.knob3.setState(states[2]) def setParams(self, params): self.knob1.SetValue(params[0]) self.knob2.SetValue(params[1]) self.knob3.SetValue(params[2]) self.preset.setByIndex(params[3]) def onChangeKnob1(self, x): if self.knob1.getState()[1] in [0, 1]: if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("audioServer").setPluginValue(self.vpos, 0, x) def onChangeKnob2(self, x): if self.knob2.getState()[1] in [0, 1]: if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("audioServer").setPluginValue(self.vpos, 1, x) def onChangeKnob3(self, x): if self.knob3.getState()[1] in [0, 1]: if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("audioServer").setPluginValue(self.vpos, 2, x) def onChangePreset(self, x, label=None): if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("audioServer").setPluginPreset(self.vpos, x, label) def createHeadBox(self): self.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) self.tw, th = self.GetTextExtent('Effects:') self.headBox = wx.BoxSizer(wx.HORIZONTAL) plugChoiceText = wx.StaticText(self, -1, 'Effects:', size=(self.tw, -1)) plugChoiceText.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoiceText.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) self.headBox.Add(plugChoiceText, 0) self.headBox.Add(98 - self.tw - 20, -1, 0) self.arrowUp = PluginArrow(self, "up", outFunction=self.arrowLeftDown) self.headBox.Add(self.arrowUp, 0) self.arrowDown = PluginArrow(self, "down", outFunction=self.arrowLeftDown) self.headBox.Add(self.arrowDown, 0) if self.vpos == 0: self.arrowUp.Hide() if self.vpos == (NUM_OF_PLUGINS - 1): self.arrowDown.Hide() self.headBox.Layout() def checkArrows(self): if self.vpos == 0: self.arrowUp.Hide() self.arrowDown.Show() self.headBox.Layout() elif self.vpos == (NUM_OF_PLUGINS - 1): self.arrowUp.Show() self.arrowDown.Hide() self.headBox.Layout() else: self.arrowUp.Show() self.arrowDown.Show() self.headBox.Layout() def arrowLeftDown(self, dir): if dir == "up": CeciliaLib.getControlPanel().movePlugin(self.vpos, -1) else: CeciliaLib.getControlPanel().movePlugin(self.vpos, 1) def cleanup(self): self.knob1.outFunction = self.knob1.parent = None self.knob2.outFunction = self.knob2.parent = None self.knob3.outFunction = self.knob3.parent = None self.choice.outFunction = None self.preset.outFunction = None self.arrowUp.outFunction = None self.arrowDown.outFunction = None class NonePlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'None' self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0, 1, 0, size=(43, 70), label='None') self.knob1.setEnable(False) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0, 1, 0, size=(43, 70), label='None') self.knob2.setEnable(False) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, 1, 0, size=(43, 70), label='None') self.knob3.setEnable(False) self.sizer.Add(self.knob3) self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='None', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['None'], init='None', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR) self.preset.setEnable(False) revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class ReverbPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Reverb' self.knobNameTemplates = ['plugin_%d_reverb_mix', 'plugin_%d_reverb_time', 'plugin_%d_reverb_damp'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0, 1, 0.25, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Mix') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0.01, 10, 1, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Time') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, 1, 0.5, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Damp') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Reverb', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_reverb_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class WGReverbPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'WGVerb' self.knobNameTemplates = ['plugin_%d_wgreverb_mix', 'plugin_%d_wgreverb_feed', 'plugin_%d_wgreverb_lp'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0, 1, 0.25, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Mix') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0., 1, 0.7, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Feed') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 100, 15000, 5000, size=(43, 70), log=True, outFunction=self.onChangeKnob3, label='Cutoff') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.knob3.setFloatPrecision(2) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='WGVerb', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_wgreverb_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class FilterPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Filter' self.knobNameTemplates = ['plugin_%d_filter_level', 'plugin_%d_filter_freq', 'plugin_%d_filter_q'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0, 2, 1, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Level') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 20, 18000, 1000, size=(43, 70), log=True, outFunction=self.onChangeKnob2, label='Freq') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.knob2.setFloatPrecision(0) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0.5, 10, 1, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Q') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Filter', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Lowpass', 'Highpass', 'Bandpass', 'Bandreject'], init='Lowpass', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_filter_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class EQPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Para EQ' self.knobNameTemplates = ['plugin_%d_eq_freq', 'plugin_%d_eq_q', 'plugin_%d_eq_gain'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 20, 18000, 1000, size=(43, 70), log=True, outFunction=self.onChangeKnob1, label='Freq') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.knob1.setFloatPrecision(0) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, .5, 10, 1, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Q') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, -48, 18, -3, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Gain') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Para EQ', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Peak/Notch', 'Lowshelf', 'Highshelf'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_eq_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class EQ3BPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = '3 Bands EQ' self.knobNameTemplates = ['plugin_%d_eq3b_low', 'plugin_%d_eq3b_mid', 'plugin_%d_eq3b_high'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, -60, 18, 0, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Low') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.knob1.setFloatPrecision(2) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, -60, 18, 0, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Mid') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.knob2.setFloatPrecision(2) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, -60, 18, 0, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='High') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.knob3.setFloatPrecision(2) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='3 Bands EQ', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_eq3b_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class ChorusPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Chorus' self.knobNameTemplates = ['plugin_%d_chorus_mix', 'plugin_%d_chorus_depth', 'plugin_%d_chorus_feed'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0, 1, 0.5, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Mix') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0.001, 5., 0.2, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Depth') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, 1, .5, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Feed') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Chorus', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Flange', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_chorus_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class CompressPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Compress' self.knobNameTemplates = ['plugin_%d_comp_thresh', 'plugin_%d_comp_ratio', 'plugin_%d_comp_gain'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, -80, 0, -20, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Thresh') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.knob1.setFloatPrecision(1) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0.125, 20, 3, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Ratio') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.knob2.setFloatPrecision(3) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, -36, 36, 0, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Gain') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Compress', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_comp_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class GatePlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Gate' self.knobNameTemplates = ['plugin_%d_gate_thresh', 'plugin_%d_gate_rise', 'plugin_%d_gate_fall'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, -120, 0, -70, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Thresh') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0.0005, .5, 0.005, size=(43, 70), log=True, outFunction=self.onChangeKnob2, label='Rise') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0.0005, .5, .01, size=(43, 70), log=True, outFunction=self.onChangeKnob3, label='Fall') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Gate', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_gate_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class DistoPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Disto' self.knobNameTemplates = ['plugin_%d_disto_drive', 'plugin_%d_disto_slope', 'plugin_%d_disto_gain'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0, 1, .7, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Drive') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0, 1, .7, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Slope') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, -60, 0, -12, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Gain') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Disto', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_disto_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class AmpModPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'AmpMod' self.knobNameTemplates = ['plugin_%d_ampmod_freq', 'plugin_%d_ampmod_amp', 'plugin_%d_ampmod_stereo'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0.01, 1000, 8, size=(43, 70), log=True, outFunction=self.onChangeKnob1, label='Freq') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0, 1, 1, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Amp') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, 0.5, 0, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Stereo') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='AmpMod', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Amplitude', 'RingMod'], init='Amplitude', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_ampmod_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class PhaserPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Phaser' self.knobNameTemplates = ['plugin_%d_phaser_freq', 'plugin_%d_phaser_q', 'plugin_%d_phaser_spread'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 20, 1000, 100, size=(43, 70), log=True, outFunction=self.onChangeKnob1, label='Freq') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.knob1.setFloatPrecision(2) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 1, 20, 5, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Q') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, .5, 2, 1.1, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Spread') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Phaser', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_phaser_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class DelayPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Delay' self.knobNameTemplates = ['plugin_%d_delay_delay', 'plugin_%d_delay_feed', 'plugin_%d_delay_mix'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0.01, 1, .1, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Delay') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0, .999, 0, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Feed') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, 1, 0.5, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Mix') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Delay', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_delay_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class FlangePlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Flange' self.knobNameTemplates = ['plugin_%d_flange_depth', 'plugin_%d_flange_freq', 'plugin_%d_flange_feed'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0.001, .99, .5, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Depth') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0.001, 20, 1, size=(43, 70), log=True, outFunction=self.onChangeKnob2, label='Freq') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, .999, 0.5, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Feed') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Flange', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_flange_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class HarmonizerPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Harmonizer' self.knobNameTemplates = ['plugin_%d_harmonizer_transpo', 'plugin_%d_harmonizer_feed', 'plugin_%d_harmonizer_mix'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, -24, 24, -7, size=(43, 70), log=False, outFunction=self.onChangeKnob1, label='Transpo') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0, .999, 0, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Feed') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, 1, 0.5, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Mix') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Harmonizer', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_harmonizer_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class ResonatorsPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'Resonators' self.knobNameTemplates = ['plugin_%d_resonators_freq', 'plugin_%d_resonators_spread', 'plugin_%d_resonators_mix'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 20, 1000, 80, size=(43, 70), log=True, outFunction=self.onChangeKnob1, label='Freq') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.knob1.setFloatPrecision(2) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, .25, 4, 2.01, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Spread') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, 1, 0.33, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Mix') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='Resonators', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_resonators_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class DeadResonPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'DeadReson' self.knobNameTemplates = ['plugin_%d_deadresonators_freq', 'plugin_%d_deadresonators_detune', 'plugin_%d_deadresonators_mix'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 20, 1000, 80, size=(43, 70), log=True, outFunction=self.onChangeKnob1, label='Freq') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.knob1.setFloatPrecision(2) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0, 1, 0.5, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Detune') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, 1, 0.33, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Mix') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='DeadReson', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Active'], init='Active', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_deadresonators_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) class ChaosModPlugin(Plugin): def __init__(self, parent, choiceFunc, order): Plugin.__init__(self, parent, choiceFunc, order) self.pluginName = 'ChaosMod' self.knobNameTemplates = ['plugin_%d_chaosmod_freq', 'plugin_%d_chaosmod_amp', 'plugin_%d_chaosmod_amp'] self.sizer = wx.FlexGridSizer(1, 4, 0, 0) revMenuBox = wx.BoxSizer(wx.VERTICAL) self.knob1 = PluginKnob(self, 0.001, 1, 0.025, size=(43, 70), log=True, outFunction=self.onChangeKnob1, label='Speed') self.knob1.setName(self.knobNameTemplates[0] % self.order) self.sizer.Add(self.knob1) self.knob2 = PluginKnob(self, 0, 1, 0.5, size=(43, 70), log=False, outFunction=self.onChangeKnob2, label='Chaos') self.knob2.setName(self.knobNameTemplates[1] % self.order) self.sizer.Add(self.knob2) self.knob3 = PluginKnob(self, 0, 1, 1, size=(43, 70), log=False, outFunction=self.onChangeKnob3, label='Amp') self.knob3.setName(self.knobNameTemplates[2] % self.order) self.sizer.Add(self.knob3) self.setKnobLabels() self.createHeadBox() revMenuBox.Add(self.headBox, 0, wx.TOP, 0) self.choice = CustomMenu(self, choice=PLUGINS_CHOICE, init='ChaosMod', size=(93, 18), colour=PLUGINPOPUP_BACK_COLOUR, outFunction=self.replacePlugin) CeciliaLib.setToolTip(self.choice, TT_POST_ITEMS) revMenuBox.Add(self.choice, 0, wx.TOP, 2) plugChoicePreset = wx.StaticText(self, -1, 'Type:') plugChoicePreset.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) plugChoicePreset.SetForegroundColour(TEXT_LABELFORWIDGET_COLOUR) revMenuBox.Add(plugChoicePreset, 0, wx.TOP, 6) self.preset = CustomMenu(self, choice=['Bypass', 'Lorenz', 'Rossler', 'ChenLee'], init='Rossler', size=(93, 18), colour=CONTROLLABEL_BACK_COLOUR, outFunction=self.onChangePreset) self.presetName = 'plugin_%d_chaosmod_preset' % self.order revMenuBox.Add(self.preset, 0, wx.TOP, 2) self.sizer.Add(revMenuBox, 0, wx.LEFT, 5) self.SetSizer(self.sizer) cecilia5-5.4.1/Resources/PreferencePanel.py000066400000000000000000000724761372272363700206150ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx, os, sys import Resources.CeciliaLib as CeciliaLib from .constants import * from .Widgets import * PADDING = 10 class PreferenceFrame(wx.Frame): def __init__(self, parent): style = (wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_FLOAT_ON_PARENT | wx.STAY_ON_TOP) wx.Frame.__init__(self, parent, style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.font = wx.Font(MENU_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) if sys.platform.startswith("linux"): self.SetClientSize((350, 400)) else: self.SetClientSize((350, 390)) panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) w, h = self.GetSize() panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) title = FrameLabel(panel, "Cecilia Preferences", size=(w - 2, 24)) box.Add(title, 0, wx.ALL, 1) headerSizer = wx.FlexGridSizer(1, 2, 5, 5) self.panelTitles = [' Paths', ' Audio', ' Midi', 'Export', 'Cecilia'] choice = PreferencesRadioToolBox(panel, size=(125, 25), outFunction=self.onPageChange) self.panelTitle = wx.StaticText(panel, -1, 'Paths') self.panelTitle.SetForegroundColour(PREFS_FOREGROUND) self.panelTitle.SetFont(self.font) headerSizer.AddMany([(choice, 0, wx.LEFT, 1), (self.panelTitle, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 90)]) box.Add(headerSizer, 0, wx.ALL, 1) box.Add(wx.StaticLine(panel, -1, size=(346, 1)), 0, wx.LEFT, 2) box.AddSpacer(5) self.panelsBox = wx.BoxSizer(wx.HORIZONTAL) pathsPane = self.createPathsPanel(panel) audioPane = self.createAudioPanel(panel) audioPane.Hide() midiPane = self.createMidiPanel(panel) midiPane.Hide() csoundPane = self.createFileExportPanel(panel) csoundPane.Hide() ceciliaPane = self.createCeciliaPanel(panel) ceciliaPane.Hide() self.panels = [pathsPane, audioPane, midiPane, csoundPane, ceciliaPane] self.currentPane = 0 self.panelsBox.Add(self.panels[self.currentPane]) box.Add(self.panelsBox, 0, wx.TOP, 10) box.AddSpacer(100) box.Add(Separator(panel), 0, wx.EXPAND | wx.BOTTOM, 10) closerBox = wx.BoxSizer(wx.HORIZONTAL) closer = CloseBox(panel, outFunction=self.onClose) closerBox.AddStretchSpacer(1) closerBox.Add(closer, 0, wx.RIGHT, PADDING) box.Add(closerBox, 0, wx.EXPAND) panel.SetSizerAndFit(box) def onClose(self, event=None): CeciliaLib.writeVarToDisk() self.Destroy() def onPageChange(self, index): self.panels[self.currentPane].Hide() self.panels[index].Show() self.panels[index].SetPosition(self.panelsBox.GetPosition()) self.panelsBox.Replace(self.panels[self.currentPane], self.panels[index]) self.currentPane = index self.panelTitle.SetLabel(self.panelTitles[self.currentPane]) wx.CallAfter(self.Refresh) def createPathsPanel(self, panel): pathsPanel = wx.Panel(panel) pathsPanel.SetBackgroundColour(BACKGROUND_COLOUR) # Soundfile Player textSfPlayerLabel = wx.StaticText(pathsPanel, -1, 'Soundfile Player :') textSfPlayerLabel.SetForegroundColour(PREFS_FOREGROUND) textSfPlayerLabel.SetFont(self.font) self.textSfPlayerPath = wx.TextCtrl(pathsPanel, -1, CeciliaLib.getVar("soundfilePlayer"), size=(267, 16), style=wx.TE_PROCESS_ENTER | wx.BORDER_NONE) self.textSfPlayerPath.SetFont(self.font) self.textSfPlayerPath.Bind(wx.EVT_TEXT_ENTER, self.handleEditPlayerPath) self.textSfPlayerPath.SetForegroundColour(PREFS_FOREGROUND) self.textSfPlayerPath.SetBackgroundColour(PREFS_PATH_BACKGROUND) buttonSfPlayerPath = CloseBox(pathsPanel, outFunction=self.changeSfPlayer, label='Set...') # Soundfile Editor textSfEditorLabel = wx.StaticText(pathsPanel, -1, 'Soundfile Editor :') textSfEditorLabel.SetForegroundColour(PREFS_FOREGROUND) textSfEditorLabel.SetFont(self.font) self.textSfEditorPath = wx.TextCtrl(pathsPanel, -1, CeciliaLib.getVar("soundfileEditor"), size=(267, 16), style=wx.TE_PROCESS_ENTER | wx.BORDER_NONE) self.textSfEditorPath.SetFont(self.font) self.textSfEditorPath.Bind(wx.EVT_TEXT_ENTER, self.handleEditEditorPath) self.textSfEditorPath.SetForegroundColour(PREFS_FOREGROUND) self.textSfEditorPath.SetBackgroundColour(PREFS_PATH_BACKGROUND) buttonSfEditorPath = CloseBox(pathsPanel, outFunction=self.changeSfEditor, label='Set...') # Text Editor textTxtEditorLabel = wx.StaticText(pathsPanel, -1, 'Text Editor :') textTxtEditorLabel.SetForegroundColour(PREFS_FOREGROUND) textTxtEditorLabel.SetFont(self.font) self.textTxtEditorPath = wx.TextCtrl(pathsPanel, -1, CeciliaLib.getVar("textEditor"), size=(267, 16), style=wx.TE_PROCESS_ENTER | wx.BORDER_NONE) self.textTxtEditorPath.SetFont(self.font) self.textTxtEditorPath.Bind(wx.EVT_TEXT_ENTER, self.handleEditTextEditorPath) self.textTxtEditorPath.SetForegroundColour(PREFS_FOREGROUND) self.textTxtEditorPath.SetBackgroundColour(PREFS_PATH_BACKGROUND) buttonTxtEditorPath = CloseBox(pathsPanel, outFunction=self.changeTxtEditor, label='Set...') # Preferred Paths textPrefPathLabel = wx.StaticText(pathsPanel, -1, 'Preferred paths :') textPrefPathLabel.SetForegroundColour(PREFS_FOREGROUND) textPrefPathLabel.SetFont(self.font) self.textPrefPath = wx.TextCtrl(pathsPanel, -1, CeciliaLib.getVar("prefferedPath"), size=(267, 16), style=wx.TE_PROCESS_ENTER | wx.BORDER_NONE) self.textPrefPath.SetFont(self.font) self.textPrefPath.Bind(wx.EVT_TEXT_ENTER, self.handleEditPrefPath) self.textPrefPath.SetForegroundColour(PREFS_FOREGROUND) self.textPrefPath.SetBackgroundColour(PREFS_PATH_BACKGROUND) buttonPrefPath = CloseBox(pathsPanel, outFunction=self.addPrefPath, label='Add...') # item, pos, span, flag, border gridSizer = wx.GridBagSizer(2, PADDING) gridSizer.Add(textSfPlayerLabel, pos=(0, 0), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=5) gridSizer.Add(self.textSfPlayerPath, pos=(1, 0), span=(1, 2), flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.EXPAND, border=5) gridSizer.Add(buttonSfPlayerPath, pos=(1, 2), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, border=5) gridSizer.Add(textSfEditorLabel, pos=(2, 0), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=5) gridSizer.Add(self.textSfEditorPath, pos=(3, 0), span=(1, 2), flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.EXPAND, border=5) gridSizer.Add(buttonSfEditorPath, pos=(3, 2), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, border=5) gridSizer.Add(textTxtEditorLabel, pos=(4, 0), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=5) gridSizer.Add(self.textTxtEditorPath, pos=(5, 0), span=(1, 2), flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.EXPAND, border=5) gridSizer.Add(buttonTxtEditorPath, pos=(5, 2), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, border=5) gridSizer.Add(textPrefPathLabel, pos=(6, 0), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=5) gridSizer.Add(self.textPrefPath, pos=(7, 0), span=(1, 2), flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.EXPAND, border=5) gridSizer.Add(buttonPrefPath, pos=(7, 2), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, border=5) pathsPanel.SetSizerAndFit(gridSizer) self.textSfPlayerPath.Navigate() return pathsPanel def createAudioPanel(self, panel): audioParamPanel = wx.Panel(panel) audioParamPanel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) driverbox = wx.BoxSizer(wx.HORIZONTAL) # Audio driver textInOutConfig = wx.StaticText(audioParamPanel, 0, 'Audio Driver :') textInOutConfig.SetForegroundColour(PREFS_FOREGROUND) textInOutConfig.SetFont(self.font) self.driverChoice = CustomMenu(audioParamPanel, choice=AUDIO_DRIVERS, init=CeciliaLib.getVar("audioHostAPI"), size=(150, 20), outFunction=self.onDriverPageChange) driverbox.Add(textInOutConfig, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) driverbox.AddStretchSpacer(1) driverbox.Add(self.driverChoice, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) # Audio driver panels # Input textIn = wx.StaticText(audioParamPanel, 0, 'Input Device :') textIn.SetForegroundColour(PREFS_FOREGROUND) textIn.SetFont(self.font) availableAudioIns = [] for d in CeciliaLib.getVar("availableAudioInputs"): availableAudioIns.append(CeciliaLib.ensureNFD(d)) try: initInput = availableAudioIns[CeciliaLib.getVar("availableAudioInputIndexes").index(CeciliaLib.getVar("audioInput"))] except: if len(availableAudioIns) >= 1: initInput = availableAudioIns[0] else: initInput = '' self.choiceInput = CustomMenu(audioParamPanel, choice=availableAudioIns, init=initInput, size=(150, 20), outFunction=self.changeAudioInput) if CeciliaLib.getVar("enableAudioInput") == 0: initInputState = 0 else: initInputState = 1 self.inputToggle = Toggle(audioParamPanel, initInputState, size=(19, 19), outFunction=self.enableAudioInput) # Output textOut = wx.StaticText(audioParamPanel, 0, 'Output Device :') textOut.SetForegroundColour(PREFS_FOREGROUND) textOut.SetFont(self.font) availableAudioOuts = [] for d in CeciliaLib.getVar("availableAudioOutputs"): availableAudioOuts.append(CeciliaLib.ensureNFD(d)) try: initOutput = availableAudioOuts[CeciliaLib.getVar("availableAudioOutputIndexes").index(CeciliaLib.getVar("audioOutput"))] except: if len(availableAudioOuts) >= 1: initOutput = availableAudioOuts[0] else: initOutput = '' self.choiceOutput = CustomMenu(audioParamPanel, choice=availableAudioOuts, init=initOutput, size=(150, 20), outFunction=self.changeAudioOutput) inbox = wx.BoxSizer(wx.HORIZONTAL) inbox.Add(textIn, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) inbox.AddStretchSpacer(1) inbox.Add(self.inputToggle, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) inbox.Add(self.choiceInput, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) outbox = wx.BoxSizer(wx.HORIZONTAL) outbox.Add(textOut, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) outbox.AddStretchSpacer(1) outbox.Add(self.choiceOutput, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) # Sample precision textSamplePrecision = wx.StaticText(audioParamPanel, 0, 'Sample Precision :') textSamplePrecision.SetForegroundColour(PREFS_FOREGROUND) textSamplePrecision.SetFont(self.font) self.choiceSamplePrecision = CustomMenu(audioParamPanel, choice=['32 bit', '64 bit'], init=CeciliaLib.getVar("samplePrecision"), size=(150, 20), outFunction=self.changeSamplePrecision) # Bit depth textBufferSize = wx.StaticText(audioParamPanel, 0, 'Buffer Size :') textBufferSize.SetForegroundColour(PREFS_FOREGROUND) textBufferSize.SetFont(self.font) self.choiceBufferSize = CustomMenu(audioParamPanel, choice=BUFFER_SIZES, init=CeciliaLib.getVar("bufferSize"), size=(150, 20), outFunction=self.changeBufferSize) # Number of channels textNCHNLS = wx.StaticText(audioParamPanel, 0, 'Default # of chnls :') textNCHNLS.SetForegroundColour(PREFS_FOREGROUND) textNCHNLS.SetFont(self.font) self.choiceNCHNLS = CustomMenu(audioParamPanel, choice=[str(x) for x in range(1, 37)], init=str(CeciliaLib.getVar("defaultNchnls")), size=(150, 20), outFunction=self.changeNchnls) # Sampling rate textSR = wx.StaticText(audioParamPanel, 0, 'Sample Rate :') textSR.SetForegroundColour(PREFS_FOREGROUND) textSR.SetFont(self.font) self.comboSR = CustomMenu(audioParamPanel, choice=SAMPLE_RATES, init=str(CeciliaLib.getVar("sr")), size=(150, 20), outFunction=self.changeSr) # First physical input textFPI = wx.StaticText(audioParamPanel, 0, 'First Physical In :') textFPI.SetForegroundColour(PREFS_FOREGROUND) textFPI.SetFont(self.font) self.choiceFPI = CustomMenu(audioParamPanel, choice=[str(x) for x in range(36)], init=str(CeciliaLib.getVar("defaultFirstInput")), size=(150, 20), outFunction=self.changeFPI) # First physical output textFPO = wx.StaticText(audioParamPanel, 0, 'First Physical Out :') textFPO.SetForegroundColour(PREFS_FOREGROUND) textFPO.SetFont(self.font) self.choiceFPO = CustomMenu(audioParamPanel, choice=[str(x) for x in range(36)], init=str(CeciliaLib.getVar("defaultFirstOutput")), size=(150, 20), outFunction=self.changeFPO) sampbox = wx.BoxSizer(wx.HORIZONTAL) sampbox.Add(textSamplePrecision, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) sampbox.AddStretchSpacer(1) sampbox.Add(self.choiceSamplePrecision, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) bufbox = wx.BoxSizer(wx.HORIZONTAL) bufbox.Add(textBufferSize, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) bufbox.AddStretchSpacer(1) bufbox.Add(self.choiceBufferSize, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) chnlbox = wx.BoxSizer(wx.HORIZONTAL) chnlbox.Add(textNCHNLS, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) chnlbox.AddStretchSpacer(1) chnlbox.Add(self.choiceNCHNLS, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) srbox = wx.BoxSizer(wx.HORIZONTAL) srbox.Add(textSR, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) srbox.AddStretchSpacer(1) srbox.Add(self.comboSR, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) finbox = wx.BoxSizer(wx.HORIZONTAL) finbox.Add(textFPI, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) finbox.AddStretchSpacer(1) finbox.Add(self.choiceFPI, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) foutbox = wx.BoxSizer(wx.HORIZONTAL) foutbox.Add(textFPO, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) foutbox.AddStretchSpacer(1) foutbox.Add(self.choiceFPO, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) box.Add(Separator(audioParamPanel, size=(350, 1), colour=BACKGROUND_COLOUR)) box.Add(driverbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(inbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(outbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(sampbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(bufbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(chnlbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(srbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(finbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(foutbox, 0, wx.EXPAND | wx.BOTTOM, 7) audioParamPanel.SetSizerAndFit(box) return audioParamPanel def createMidiPanel(self, panel): midiParamPanel = wx.Panel(panel) midiParamPanel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) driverbox = wx.BoxSizer(wx.HORIZONTAL) # Midi driver textInOutConfig = wx.StaticText(midiParamPanel, 0, 'Midi Driver :') textInOutConfig.SetForegroundColour(PREFS_FOREGROUND) textInOutConfig.SetFont(self.font) self.midiDriverChoice = CustomMenu(midiParamPanel, choice=['PortMidi'], size=(150, 20), init='PortMidi', outFunction=self.onMidiDriverPageChange) driverbox.Add(textInOutConfig, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) driverbox.AddStretchSpacer(1) driverbox.Add(self.midiDriverChoice, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) # Input textIn = wx.StaticText(midiParamPanel, 0, 'Input Device :') textIn.SetForegroundColour(PREFS_FOREGROUND) textIn.SetFont(self.font) availableMidiIns = [] for d in CeciliaLib.getVar("availableMidiInputs"): availableMidiIns.append(CeciliaLib.ensureNFD(d)) try: initInput = availableMidiIns[CeciliaLib.getVar("availableMidiInputIndexes").index(CeciliaLib.getVar("midiDeviceIn"))] except: if len(availableMidiIns) >= 1: initInput = availableMidiIns[0] else: initInput = '' self.midiChoiceInput = CustomMenu(midiParamPanel, choice=availableMidiIns, init=initInput, size=(150, 20), outFunction=self.changeMidiInput) inbox = wx.BoxSizer(wx.HORIZONTAL) inbox.Add(textIn, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) inbox.AddStretchSpacer(1) inbox.Add(self.midiChoiceInput, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) textAutoBinding = wx.StaticText(midiParamPanel, 0, 'Automatic Midi Bindings :') textAutoBinding.SetForegroundColour(PREFS_FOREGROUND) textAutoBinding.SetFont(self.font) self.autoMidiToggle = Toggle(midiParamPanel, CeciliaLib.getVar("automaticMidiBinding"), size=(19, 19), outFunction=self.enableAutomaticBinding) autobox = wx.BoxSizer(wx.HORIZONTAL) autobox.Add(textAutoBinding, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) autobox.AddStretchSpacer(1) autobox.Add(self.autoMidiToggle, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING + 1) box.Add(Separator(midiParamPanel, size=(350, 1), colour=BACKGROUND_COLOUR)) box.Add(driverbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(inbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(autobox, 0, wx.EXPAND | wx.BOTTOM, 7) midiParamPanel.SetSizerAndFit(box) return midiParamPanel def createFileExportPanel(self, panel): fileExportPanel = wx.Panel(panel) fileExportPanel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) # File Format textFileFormat = wx.StaticText(fileExportPanel, 0, 'File Format :') textFileFormat.SetForegroundColour(PREFS_FOREGROUND) textFileFormat.SetFont(self.font) self.choiceFileFormat = CustomMenu(fileExportPanel, choice=sorted(AUDIO_FILE_FORMATS.keys()), init=CeciliaLib.getVar("audioFileType"), size=(150, 20), outFunction=self.changeFileType) # Bit depth textBD = wx.StaticText(fileExportPanel, 0, 'Bit Depth :') textBD.SetForegroundColour(PREFS_FOREGROUND) textBD.SetFont(self.font) self.choiceBD = CustomMenu(fileExportPanel, choice=sorted(BIT_DEPTHS.keys()), size=(150, 20), outFunction=self.changeSampSize) for item in BIT_DEPTHS.items(): if item[1] == CeciliaLib.getVar("sampSize"): self.choiceBD.setStringSelection(item[0]) formatbox = wx.BoxSizer(wx.HORIZONTAL) formatbox.Add(textFileFormat, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) formatbox.AddStretchSpacer(1) formatbox.Add(self.choiceFileFormat, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) depthbox = wx.BoxSizer(wx.HORIZONTAL) depthbox.Add(textBD, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) depthbox.AddStretchSpacer(1) depthbox.Add(self.choiceBD, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) box.Add(Separator(fileExportPanel, size=(350, 1), colour=BACKGROUND_COLOUR)) box.Add(formatbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(depthbox, 0, wx.EXPAND | wx.BOTTOM, 7) fileExportPanel.SetSizerAndFit(box) return fileExportPanel def createCeciliaPanel(self, panel): ceciliaPanel = wx.Panel(panel) ceciliaPanel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) textTotalTime = wx.StaticText(ceciliaPanel, 0, 'Default totaltime :') textTotalTime.SetForegroundColour(PREFS_FOREGROUND) textTotalTime.SetFont(self.font) self.choiceTotalTime = CustomMenu(ceciliaPanel, size=(150, 20), choice=["10.0", "30.0", "60.0", "120.0", "300.0", "600.0", "1200.0", "2400.0", "3600.0"], init=str(CeciliaLib.getVar("defaultTotalTime")), outFunction=self.changeDefaultTotalTime) textGlobalFade = wx.StaticText(ceciliaPanel, 0, 'Global fade time :') textGlobalFade.SetForegroundColour(PREFS_FOREGROUND) textGlobalFade.SetFont(self.font) self.choiceGlobalFade = CustomMenu(ceciliaPanel, size=(150, 20), choice=["0.0", "0.001", "0.002", "0.003", "0.004", "0.005", "0.01", "0.015", "0.02", "0.025", "0.03", "0.05", "0.075", "0.1", "0.2", "0.3", "0.4", "0.5"], init=str(CeciliaLib.getVar("globalFade")), outFunction=self.changeGlobalFade) textUseTooltips = wx.StaticText(ceciliaPanel, 0, 'Use tooltips :') textUseTooltips.SetForegroundColour(PREFS_FOREGROUND) textUseTooltips.SetFont(self.font) self.tooltipsToggle = Toggle(ceciliaPanel, CeciliaLib.getVar("useTooltips"), size=(19, 19), outFunction=self.enableTooltips) textgraphTexture = wx.StaticText(ceciliaPanel, 0, 'Use grapher texture :') textgraphTexture.SetForegroundColour(PREFS_FOREGROUND) textgraphTexture.SetFont(self.font) self.textureToggle = Toggle(ceciliaPanel, CeciliaLib.getVar("graphTexture"), size=(19, 19), outFunction=self.enableGraphTexture) textVerbose = wx.StaticText(ceciliaPanel, 0, 'Verbose :') textVerbose.SetForegroundColour(PREFS_FOREGROUND) textVerbose.SetFont(self.font) self.verboseToggle = Toggle(ceciliaPanel, CeciliaLib.getVar("DEBUG"), size=(19, 19), outFunction=self.enableVerbose) timebox = wx.BoxSizer(wx.HORIZONTAL) timebox.Add(textTotalTime, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) timebox.AddStretchSpacer(1) timebox.Add(self.choiceTotalTime, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) fadebox = wx.BoxSizer(wx.HORIZONTAL) fadebox.Add(textGlobalFade, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) fadebox.AddStretchSpacer(1) fadebox.Add(self.choiceGlobalFade, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING) tipsbox = wx.BoxSizer(wx.HORIZONTAL) tipsbox.Add(textUseTooltips, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) tipsbox.AddStretchSpacer(1) tipsbox.Add(self.tooltipsToggle, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING + 1) graphbox = wx.BoxSizer(wx.HORIZONTAL) graphbox.Add(textgraphTexture, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) graphbox.AddStretchSpacer(1) graphbox.Add(self.textureToggle, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING + 1) verbbox = wx.BoxSizer(wx.HORIZONTAL) verbbox.Add(textVerbose, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) verbbox.AddStretchSpacer(1) verbbox.Add(self.verboseToggle, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, PADDING + 1) box.Add(Separator(ceciliaPanel, size=(350, 1), colour=BACKGROUND_COLOUR)) box.Add(timebox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(fadebox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(tipsbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(graphbox, 0, wx.EXPAND | wx.BOTTOM, 7) box.Add(verbbox, 0, wx.EXPAND | wx.BOTTOM, 7) ceciliaPanel.SetSizerAndFit(box) return ceciliaPanel def onDriverPageChange(self, index, label): CeciliaLib.setVar("audioHostAPI", label) def onMidiDriverPageChange(self, index, label): pass def enableAudioInput(self, state): CeciliaLib.setVar('enableAudioInput', state) def changeAudioInput(self, index, label): deviceIndex = CeciliaLib.getVar("availableAudioInputIndexes")[index] CeciliaLib.setVar("audioInput", deviceIndex) def changeAudioOutput(self, index, label): deviceIndex = CeciliaLib.getVar("availableAudioOutputIndexes")[index] CeciliaLib.setVar("audioOutput", deviceIndex) def changeMidiInput(self, index, label): deviceIndex = CeciliaLib.getVar("availableMidiInputIndexes")[index] CeciliaLib.setVar("midiDeviceIn", deviceIndex) def changeSfPlayer(self): CeciliaLib.loadPlayerEditor("soundfile player") self.textSfPlayerPath.SetValue(CeciliaLib.getVar("soundfilePlayer")) def changeSfEditor(self): CeciliaLib.loadPlayerEditor("soundfile editor") self.textSfEditorPath.SetValue(CeciliaLib.getVar("soundfileEditor")) def changeTxtEditor(self): CeciliaLib.loadPlayerEditor("text editor") self.textTxtEditorPath.SetValue(CeciliaLib.getVar("textEditor")) def addPrefPath(self): currentPath = CeciliaLib.getVar("prefferedPath") path = '' dlg = wx.DirDialog(self, message="Choose a folder...", defaultPath=CeciliaLib.ensureNFD(os.path.expanduser('~'))) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() dlg.Destroy() if path and currentPath != '': path = currentPath + ';' + path elif not path: return CeciliaLib.setVar("prefferedPath", path) self.textPrefPath.SetValue(path) def handleEditPlayerPath(self, event): path = self.textSfPlayerPath.GetValue() CeciliaLib.setVar("soundfilePlayer", path) self.textSfPlayerPath.Navigate() def handleEditEditorPath(self, event): path = self.textSfEditorPath.GetValue() CeciliaLib.setVar("soundfileEditor", path) self.textSfEditorPath.Navigate() def handleEditTextEditorPath(self, event): path = self.textTxtEditorPath.GetValue() CeciliaLib.setVar("textEditor", path) self.textTxtEditorPath.Navigate() def handleEditPrefPath(self, event): path = self.textPrefPath.GetValue() CeciliaLib.setVar("prefferedPath", path) self.textPrefPath.Navigate() def changeSamplePrecision(self, index, label): CeciliaLib.setVar("samplePrecision", label) def changeBufferSize(self, index, label): CeciliaLib.setVar("bufferSize", label) def changeFileType(self, index, label): CeciliaLib.setVar("audioFileType", label) def changeSr(self, index, label): sr = int(label.strip()) CeciliaLib.setVar("sr", sr) def changeSampSize(self, index, label): CeciliaLib.setVar("sampSize", BIT_DEPTHS[label]) def changeNchnls(self, index, choice): nchnls = int(choice) CeciliaLib.setVar("defaultNchnls", nchnls) CeciliaLib.setVar("nchnls", nchnls) CeciliaLib.updateNchnlsDevices() def changeFPI(self, index, choice): CeciliaLib.setVar("defaultFirstInput", index) def changeFPO(self, index, choice): CeciliaLib.setVar("defaultFirstOutput", index) def changeDefaultTotalTime(self, index, label): CeciliaLib.setVar("defaultTotalTime", float(self.choiceTotalTime.getLabel().strip())) def changeGlobalFade(self, index, label): CeciliaLib.setVar("globalFade", float(self.choiceGlobalFade.getLabel().strip())) def enableTooltips(self, state): CeciliaLib.setVar("useTooltips", state) CeciliaLib.updateTooltips() def enableGraphTexture(self, state): CeciliaLib.setVar("graphTexture", state) if CeciliaLib.getVar("grapher") is not None: CeciliaLib.getVar("grapher").plotter.draw() def enableAutomaticBinding(self, state): CeciliaLib.setVar("automaticMidiBinding", state) def enableVerbose(self, state): CeciliaLib.setVar("DEBUG", state) CeciliaLib.getVar("audioServer").updateDebug() cecilia5-5.4.1/Resources/Preset.py000066400000000000000000000134651372272363700170120ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx, os import Resources.CeciliaLib as CeciliaLib from .constants import * from .Widgets import * class CECPreset(wx.Panel): def __init__(self, parent, id=-1, size=(-1, -1), style=wx.BORDER_SIMPLE): wx.Panel.__init__(self, parent, id, size=size, style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.currentPreset = 'init' mainSizer = wx.FlexGridSizer(0, 1, 0, 0) mainSizer.Add(10, 1, 0) presetTextPanel = wx.Panel(self, -1, style=wx.BORDER_NONE) presetTextPanel.SetBackgroundColour(TITLE_BACK_COLOUR) presetTextSizer = wx.FlexGridSizer(1, 1, 0, 0) presetText = wx.StaticText(presetTextPanel, -1, 'PRESETS') presetText.SetFont(wx.Font(SECTION_TITLE_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) presetText.SetBackgroundColour(TITLE_BACK_COLOUR) presetText.SetForegroundColour(SECTION_TITLE_COLOUR) presetTextSizer.Add(presetText, 0, wx.ALIGN_RIGHT | wx.ALL, 3) presetTextSizer.AddGrowableCol(0) presetTextPanel.SetSizer(presetTextSizer) mainSizer.Add(presetTextPanel, 1, wx.EXPAND, 0) lineSizer = wx.BoxSizer(wx.HORIZONTAL) self.presetChoice = CustomMenu(self, choice=self.orderingPresetNames(), size=(150, 20), init=self.currentPreset, outFunction=self.onPresetSelect, colour=TR_BACK_COLOUR) CeciliaLib.setToolTip(self.presetChoice, TT_PRESET) lineSizer.Add(self.presetChoice, 0, wx.ALIGN_LEFT, 1) lineSizer.Add(10, 1, 0) self.saveTool = ToolBox(self, tools=['save', 'delete'], outFunction=[self.onSavePreset, self.onDeletePreset]) CeciliaLib.setToolTip(self.saveTool, TT_PRESET_TOOLS) lineSizer.Add(self.saveTool, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 2) mainSizer.Add(lineSizer, 0, wx.ALIGN_CENTER | wx.ALL, 7) mainSizer.AddGrowableCol(0) self.SetSizer(mainSizer) def cleanup(self): self.presetChoice.cleanup() self.saveTool.cleanup() def isPreset(self, preset): return os.path.isfile(os.path.join(PRESETS_PATH, CeciliaLib.getVar("currentModuleName"), preset)) def getPresetPath(self, preset): return os.path.join(PRESETS_PATH, CeciliaLib.getVar("currentModuleName"), preset) def getPresets(self): return self.presetChoice.getChoice() def setLabel(self, label): if label in self.getPresets(): self.presetChoice.setLabel(label, False) self.currentPreset = label def loadPresets(self): presets = self.orderingPresetNames() self.presetChoice.setChoice(presets, False) def orderingPresetNames(self): presets = [] presetsDir = os.path.join(PRESETS_PATH, CeciliaLib.getVar("currentModuleName")) if os.path.isdir(presetsDir): presets.extend(os.listdir(presetsDir)) presets.sort() presets.insert(0, 'init') return presets def onPresetSelect(self, idxPreset, newPreset): if newPreset == 'init': CeciliaLib.loadPresetFromFile("init") self.currentPreset = "init" elif self.isPreset(newPreset): CeciliaLib.loadPresetFromFile(newPreset) self.currentPreset = newPreset def onDeletePreset(self): if self.isPreset(self.currentPreset): dlg = wx.MessageDialog(self, 'Preset %s will be deleted. Are you sure?' % self.currentPreset, 'Warning!', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION) ok = dlg.ShowModal() != wx.ID_NO dlg.Destroy() if ok: os.remove(self.getPresetPath(self.currentPreset)) self.presetChoice.setChoice(self.orderingPresetNames(), False) self.presetChoice.setStringSelection("") def onSavePreset(self): dlg = wx.TextEntryDialog(self, 'Enter preset name:', 'Saving Preset', self.currentPreset) if dlg.ShowModal() == wx.ID_OK: newPreset = CeciliaLib.ensureNFD(dlg.GetValue()) else: newPreset = '' dlg.Destroy() if newPreset == '': CeciliaLib.showErrorDialog('Failed saving preset', 'You must give a name to your preset!') return if newPreset == 'init': CeciliaLib.showErrorDialog('Failed saving preset', '"init" is reserved. You must give another name to your preset!') return ok = True if newPreset in self.getPresets(): dlg2 = wx.MessageDialog(self, 'The preset you entered already exists. Are you sure you want to overwrite it?', 'Existing preset!', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION) if dlg2.ShowModal() == wx.ID_NO: ok = False dlg2.Destroy() if ok: self.currentPreset = newPreset CeciliaLib.savePresetToFile(self.currentPreset) self.loadPresets() self.setLabel(self.currentPreset) cecilia5-5.4.1/Resources/Sliders.py000066400000000000000000002155031372272363700171520ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx, math import Resources.CeciliaLib as CeciliaLib from .constants import * from .Widgets import * class PlayRecButtons(wx.Panel): def __init__(self, parent, cecslider, id=wx.ID_ANY, pos=(0, 0), size=(40, 20)): wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.cecslider = cecslider self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.enterWithButtonDown = False self.playColour = SLIDER_PLAY_COLOUR_HOT self.recColour = SLIDER_REC_COLOUR_HOT self.playOver = False self.recOver = False self.playOverWait = True self.recOverWait = True self.play = 0 self.rec = False if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def cleanup(self): self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.Unbind(wx.EVT_MOTION, handler=self.OnMotion) self.Unbind(wx.EVT_LEAVE_WINDOW, handler=self.OnLeave) self.Unbind(wx.EVT_ENTER_WINDOW, handler=self.OnEnter) self.Unbind(wx.EVT_LEFT_DOWN, handler=self.MouseDown) self.Unbind(wx.EVT_LEFT_UP, handler=self.MouseUp) self.cecslider = None def setOverWait(self, which): if which == 0: self.playOverWait = False elif which == 1: self.recOverWait = False def checkForOverReady(self, pos): if not wx.Rect(2, 2, 17, 17).Contains(pos): self.playOverWait = True if not wx.Rect(21, 2, 38, 17).Contains(pos): self.recOverWait = True def MouseDown(self, evt): if not CeciliaLib.getVar("audioServer").isAudioServerRunning(): pos = evt.GetPosition() if wx.Rect(2, 2, 17, 17).Contains(pos): self.play = (self.play + 1) % 3 self.setPlay(self.play) self.setOverWait(0) elif wx.Rect(21, 2, 38, 17).Contains(pos): if self.rec: self.setRec(False) else: self.setRec(True) self.setOverWait(1) self.playOver = False self.recOver = False wx.CallAfter(self.Refresh) evt.Skip() def OnEnter(self, evt): if evt.LeftIsDown() and not CeciliaLib.getVar("audioServer").isAudioServerRunning(): self.enterWithButtonDown = True pos = evt.GetPosition() if wx.Rect(0, 0, 20, 20).Contains(pos): self.play = (self.play + 1) % 3 self.setPlay(self.play) elif wx.Rect(20, 0, 40, 20).Contains(pos): if self.rec: self.setRec(False) else: self.setRec(True) self.playOver = False self.recOver = False wx.CallAfter(self.Refresh) evt.Skip() def MouseUp(self, evt): self.enterWithButtonDown = False def OnMotion(self, evt): if not CeciliaLib.getVar("audioServer").isAudioServerRunning() and not self.enterWithButtonDown: pos = evt.GetPosition() if wx.Rect(2, 2, 17, 17).Contains(pos) and self.playOverWait: self.playOver = True self.recOver = False elif wx.Rect(21, 2, 38, 17).Contains(pos) and self.recOverWait: self.playOver = False self.recOver = True self.checkForOverReady(pos) wx.CallAfter(self.Refresh) evt.Skip() def OnLeave(self, evt): self.enterWithButtonDown = False self.playOver = False self.recOver = False self.playOverWait = True self.recOverWait = True wx.CallAfter(self.Refresh) evt.Skip() def OnPaint(self, evt): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) # Draw triangle if self.playOver: playColour = SLIDER_PLAY_COLOUR_OVER else: playColour = self.playColour gc.SetPen(wx.Pen(playColour, width=1, style=wx.SOLID)) gc.SetBrush(wx.Brush(playColour, wx.SOLID)) tri = [(14, h / 2), (9, 4), (9, h - 4), (14, h / 2)] gc.DrawLines(tri) dc.SetPen(wx.Pen('#333333', width=1, style=wx.SOLID)) dc.DrawLine(w // 2, 4, w // 2, h - 4) # Draw circle if self.recOver: recColour = SLIDER_REC_COLOUR_OVER else: recColour = self.recColour gc.SetPen(wx.Pen(recColour, width=1, style=wx.SOLID)) gc.SetBrush(wx.Brush(recColour, wx.SOLID)) gc.DrawEllipse(w / 4 + w / 2 - 4, h / 2 - 4, 8, 8) evt.Skip() def setPlay(self, x): self.play = x if self.play == 0: self.playColour = SLIDER_PLAY_COLOUR_HOT elif self.play == 1: if self.rec: self.setRec(0) self.playColour = SLIDER_PLAY_COLOUR_PRESSED else: if self.rec: self.setRec(0) self.playColour = SLIDER_PLAY_COLOUR_NO_BIND wx.CallAfter(self.Refresh) def getPlay(self): return self.play def getRec(self): return self.rec def setRec(self, x): if x == 0: self.rec = False self.recColour = SLIDER_REC_COLOUR_HOT else: if self.play > 0: self.setPlay(0) self.rec = True self.recColour = SLIDER_REC_COLOUR_PRESSED class Slider(wx.Panel): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(200, 20), valtype='float', log=False, function=None, cecslider=None): wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size, style=wx.BORDER_NONE) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.SetMinSize(self.GetSize()) self.outFunction = function self.cecslider = cecslider if valtype.startswith('i'): self.myType = int else: self.myType = float self.log = log self.SetRange(minvalue, maxvalue) self.borderWidth = 1 self.fillcolor = SLIDER_BACK_COLOUR self.knobcolor = SLIDER_KNOB_COLOUR self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.Bind(wx.EVT_MOTION, self.MouseMotion) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnResize) def cleanup(self): self.Unbind(wx.EVT_LEFT_DOWN, handler=self.MouseDown) self.Unbind(wx.EVT_LEFT_UP, handler=self.MouseUp) self.Unbind(wx.EVT_MOTION, handler=self.MouseMotion) self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.Unbind(wx.EVT_SIZE, handler=self.OnResize) self.outFunction = None self.cecslider = None def createSliderBitmap(self): w, h = self.GetSize() b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=1)) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR)) dc.DrawRectangle(0, 0, w, h) dc.SetBrush(wx.Brush("#777777")) dc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) h2 = self.sliderHeight // 4 dc.DrawRoundedRectangle(0, h2, w, self.sliderHeight, 4) dc.SelectObject(wx.NullBitmap) b.SetMaskColour("#777777") self.sliderMask = b def createKnobMaskBitmap(self): w, h = self.knobSize, self.GetSize()[1] b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) rec = wx.Rect(0, 0, w, h) dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=1)) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR)) dc.DrawRectangle(rec) h2 = self.sliderHeight // 4 rec = wx.Rect(0, h2, w, self.sliderHeight) dc.GradientFillLinear(rec, GRADIENT_DARK_COLOUR, self.fillcolor, wx.BOTTOM) dc.SetBrush(wx.Brush("#787878")) dc.SetPen(wx.Pen(KNOB_BORDER_COLOUR, width=1)) dc.DrawRoundedRectangle(0, 0, w, h, 3) dc.SelectObject(wx.NullBitmap) b.SetMaskColour("#787878") self.knobMask = b def setFillColour(self, col1, col2): self.fillcolor = col1 self.knobcolor = col2 self.createBackgroundBitmap() self.createKnobBitmap() def SetRange(self, minvalue, maxvalue): self.minvalue = minvalue self.maxvalue = maxvalue def scale(self): inter = CeciliaLib.tFromValue(self.pos, self.knobHalfSize, self.GetSize()[0] - self.knobHalfSize) return CeciliaLib.interpFloat(inter, self.minvalue, self.maxvalue) def MouseDown(self, evt): size = self.GetSize() self.pos = CeciliaLib.clamp(evt.GetPosition()[0], self.knobHalfSize, size[0] - self.knobHalfSize) self.value = self.scale() self.CaptureMouse() wx.CallAfter(self.Refresh) def MouseMotion(self, evt): size = self.GetSize() if evt.Dragging() and evt.LeftIsDown() and self.HasCapture(): self.pos = CeciliaLib.clamp(evt.GetPosition()[0], self.knobHalfSize, size[0] - self.knobHalfSize) self.value = self.scale() wx.CallAfter(self.Refresh) def MouseUp(self, evt): if self.HasCapture(): self.ReleaseMouse() if self.cecslider.getUp() and CeciliaLib.getVar("currentModule") is not None: getattr(CeciliaLib.getVar("currentModule"), self.cecslider.name + "_up")(self.GetValue()) def OnResize(self, evt): self.createSliderBitmap() self.createKnobMaskBitmap() self.createKnobBitmap() self.createBackgroundBitmap() self.clampPos() #wx.CallAfter(self.Refresh) self.Refresh() def clampPos(self): size = self.GetSize() self.pos = CeciliaLib.tFromValue(self.value, self.minvalue, self.maxvalue) * (size[0] - self.knobSize) + self.knobHalfSize self.pos = CeciliaLib.clamp(self.pos, self.knobHalfSize, size[0] - self.knobHalfSize) class HSlider(Slider): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(200, 15), valtype='float', log=False, function=None, cecslider=None): Slider.__init__(self, parent, minvalue, maxvalue, init, pos, size, valtype, log, function, cecslider) self.SetMinSize((50, 15)) if self.cecslider: if self.cecslider.half: self.knobSize = 22 self.knobHalfSize = 11 else: self.knobSize = 28 self.knobHalfSize = 14 else: self.knobSize = 28 self.knobHalfSize = 14 self.sliderHeight = 14 self.createKnobMaskBitmap() self.createSliderBitmap() self.createKnobBitmap() self.createBackgroundBitmap() self.value = 0 if init is not None: self._setValue(init) else: self._setValue(minvalue) self.clampPos() self.midictl = '' self.midiLearn = False self.openSndCtrl = '' self.font = wx.Font(LABEL_FONT - 2, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT) self.mario = 0 self.useMario = False self.marios = [CeciliaLib.getVar("ICON_MARIO1"), CeciliaLib.getVar("ICON_MARIO2"), CeciliaLib.getVar("ICON_MARIO3"), CeciliaLib.getVar("ICON_MARIO4"), CeciliaLib.getVar("ICON_MARIO5"), CeciliaLib.getVar("ICON_MARIO6")] def setSliderHeight(self, height): self.sliderHeight = height self.createSliderBitmap() self.createKnobMaskBitmap() self.createBackgroundBitmap() self.createKnobBitmap() wx.CallAfter(self.Refresh) def createBackgroundBitmap(self): w, h = self.GetSize() self.backgroundBitmap = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(self.backgroundBitmap) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=self.borderWidth, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) # Draw inner part h2 = self.sliderHeight // 4 rec = wx.Rect(0, h2, w, self.sliderHeight) dc.GradientFillLinear(rec, GRADIENT_DARK_COLOUR, self.fillcolor, wx.BOTTOM) dc.DrawBitmap(self.sliderMask, 0, 0, True) dc.SelectObject(wx.NullBitmap) def createKnobBitmap(self): w, h = self.GetSize() self.knobBitmap = wx.EmptyBitmap(self.knobSize, h) dc = wx.MemoryDC(self.knobBitmap) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() rec = wx.Rect(0, 0, self.knobSize, h) dc.GradientFillLinear(rec, GRADIENT_DARK_COLOUR, self.knobcolor, wx.RIGHT) dc.DrawBitmap(self.knobMask, rec[0], rec[1], True) dc.SelectObject(wx.NullBitmap) def setMidiCtl(self, str): self.midictl = str self.midiLearn = False def inMidiLearnMode(self): self.midiLearn = True wx.CallAfter(self.Refresh) def setOpenSndCtrl(self, str): self.openSndCtrl = str wx.CallAfter(self.Refresh) def _setValue(self, value): self.lastvalue = self.value value = CeciliaLib.clamp(value, self.minvalue, self.maxvalue) if self.log: t = CeciliaLib.toLog(value, self.minvalue, self.maxvalue) self.value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) else: t = CeciliaLib.tFromValue(value, self.minvalue, self.maxvalue) self.value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) if self.myType == int: self.value = int(self.value) self.clampPos() def SetValue(self, value): self._setValue(value) wx.CallAfter(self.Refresh) def GetValue(self): if self.log: t = CeciliaLib.tFromValue(self.value, self.minvalue, self.maxvalue) val = CeciliaLib.toExp(t, self.minvalue, self.maxvalue) else: val = self.value if self.myType == int: return int(val) return val def OnPaint(self, evt): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetFont(self.font) dc.SetTextForeground(LABEL_LABEL_COLOUR) pos = self.pos - self.knobHalfSize dc.DrawBitmap(self.backgroundBitmap, 0, 0) # Draw knob if not self.useMario: dc.DrawBitmap(self.knobBitmap, pos, 0) else: if self.lastvalue < self.value: marioff = 0 else: marioff = 3 bitmario = self.marios[(self.mario % 3) + marioff] dc.DrawBitmap(bitmario, self.pos - 8, 0, True) self.mario += 1 if CeciliaLib.getVar("systemPlatform") == "win32": dc.SetFont(wx.Font(LABEL_FONT - 1, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) else: dc.SetFont(wx.Font(LABEL_FONT - 1, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT)) if self.midiLearn: dc.DrawLabel("Move a MIDI controller...", wx.Rect(5, 0, 50, h), wx.ALIGN_CENTER_VERTICAL) elif self.openSndCtrl: dc.DrawLabel(self.openSndCtrl, wx.Rect(5, 0, w, h), wx.ALIGN_CENTER_VERTICAL) else: dc.DrawLabel(self.midictl, wx.Rect(5, 0, w, h), wx.ALIGN_CENTER_VERTICAL) # Send value if self.outFunction: self.outFunction(self.GetValue()) class CECSlider: def __init__(self, parent, minvalue, maxvalue, init=None, label='slider', unit='', valtype='float', log=False, name='', gliss=.025, midictl=None, tooltip='', up=False, half=False): self.widget_type = "slider" self.parent = parent self.valtype = valtype self.name = name self.half = half self.gliss = gliss self.automationLength = None self.automationData = [] self.path = None self.openSndCtrl = None self.OSCOut = None self.midictl = None self.midichan = 1 self.minvalue = minvalue self.maxvalue = maxvalue self.log = log self.up = up self.convertSliderValue = 200 self.path = os.path.join(AUTOMATION_SAVE_PATH, self.name) pos = (0, 0) size = (200, 16) self.slider = HSlider(parent, minvalue, maxvalue, init, pos, size, valtype, log, self.writeToEntry, self) self.slider.setSliderHeight(11) self.setMidiCtl(midictl) self.label = Label(parent, label, size=(100, 16), outFunction=self.onLabelClick, dclickFunction=self.onLabelDClick) CeciliaLib.setToolTip(self.label, TT_SLIDER_LABEL) if self.half: unitX = 100 else: unitX = 120 self.entryUnit = EntryUnit(parent, self.slider.GetValue(), unit, size=(unitX, 16), valtype=valtype, outFunction=self.entryReturn) CeciliaLib.setToolTip(self.entryUnit, TT_SLIDER_DISPLAY) self.buttons = PlayRecButtons(parent, self, size=(40, 16)) CeciliaLib.setToolTip(self.buttons, TT_SLIDER_AUTO) def cleanup(self): self.entryUnit.cleanup() self.slider.cleanup() self.label.cleanup() self.buttons.cleanup() def refresh(self): wx.CallAfter(self.slider.Refresh) wx.CallAfter(self.label.Refresh) wx.CallAfter(self.entryUnit.Refresh) def setFillColour(self, col1, col2, col3): self.slider.setFillColour(col1, col2) self.label.setBackColour(col1) self.entryUnit.setBackColour(col1) def onLabelClick(self, label, shift=False, alt=False, side='left'): if not self.up: # alt is now the right click if alt and shift: self.setMidiCtl(None) elif shift: CeciliaLib.getVar("grapher").setShowLineSolo(label) CeciliaLib.getVar("grapher").toolbar.menu.setLabel(label, True) elif alt: if CeciliaLib.getVar("useMidi"): CeciliaLib.getVar("grapher").toolbar.menu.setLabel(label, True) CeciliaLib.getVar("audioServer").midiLearn(self) self.slider.inMidiLearnMode() else: CeciliaLib.showErrorDialog("Midi not initialized!", "There is no Midi interface connected!") else: CeciliaLib.getVar("grapher").resetShow() CeciliaLib.getVar("grapher").toolbar.menu.setLabel(label, True) def onLabelDClick(self, side='left'): f = OSCPopupFrame(CeciliaLib.getVar('interface'), self) f.CenterOnParent() f.Show() def setOSCInput(self, value): self.setOpenSndCtrl(value) def setOSCOutput(self, value): self.setOSCOut(value) def setConvertSliderValue(self, x, end=None): self.convertSliderValue = x def setAutomationLength(self, x): self.automationLength = x def getAutomationLength(self): return self.automationLength def sendValue(self, value): if self.getPlay() in [0, 1] or self.getRec() == 1: if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._sliders[self.name].setValue(value) def entryReturn(self, value): self.slider.SetValue(value) self.sendValue(value) def writeToEntry(self, value): if self.slider.myType == float: if value >= 10000: self.entryUnit.setValue(float('%5.2f' % value)) elif value >= 1000: self.entryUnit.setValue(float('%5.3f' % value)) elif value >= 10: self.entryUnit.setValue(float('%5.5f' % value)) elif value >= 0: self.entryUnit.setValue(float('%5.5f' % value)) elif value >= -100: self.entryUnit.setValue(float('%5.5f' % value)) elif value >= -1000: self.entryUnit.setValue(float('%5.4f' % value)) elif value >= -10000: self.entryUnit.setValue(float('%5.3f' % value)) else: self.entryUnit.setValue(float('%5.2f' % value)) else: self.entryUnit.setValue(value) self.sendValue(value) def getUp(self): return self.up def setValue(self, value): self.slider.SetValue(value) def getValue(self): return self.slider.GetValue() def getLog(self): return self.log def getMinValue(self): return self.minvalue def getMaxValue(self): return self.maxvalue def getName(self): return self.name def setPlay(self, x): self.buttons.setPlay(x) def setRec(self, x): self.buttons.setRec(x) def getPlay(self): return self.buttons.getPlay() def getRec(self): return self.buttons.getRec() def getState(self): return [self.getValue(), self.getPlay(), self.getMidiCtl(), self.getMidiChannel(), self.getOpenSndCtrl(), self.getOSCOut()] def setState(self, values): self.setValue(values[0]) self.setPlay(values[1]) if len(values) >= 4: self.setMidiChannel(values[3]) self.setMidiCtl(values[2]) if len(values) >= 5: self.setOpenSndCtrl(values[4]) if len(values) >= 6: self.setOSCOut(values[5]) def getPath(self): return self.path def setMidiCtl(self, ctl): if ctl is None: self.midictl = None self.midichan = 1 self.slider.setMidiCtl('') else: self.midictl = int(ctl) self.slider.setMidiCtl("%d:%d" % (self.midictl, self.midichan)) self.openSndCtrl = None self.slider.setOpenSndCtrl('') wx.CallAfter(self.slider.Refresh) def getMidiCtl(self): return self.midictl def setMidiChannel(self, chan): self.midichan = int(chan) def getMidiChannel(self): return self.midichan def getWithMidi(self): if self.getMidiCtl() is not None and CeciliaLib.getVar("useMidi"): return True else: return False def setOpenSndCtrl(self, value): if value is None or value == "": self.openSndCtrl = None self.slider.setOpenSndCtrl("") else: if type(value) == tuple: msg = "%s:%s" % (value[0], value[1]) else: msg = value sep = msg.split(":") port = int(sep[0].strip()) address = str(sep[1].strip()) self.openSndCtrl = (port, address) self.slider.setOpenSndCtrl("%d:%s" % (port, address)) self.setMidiCtl(None) def setOSCOut(self, value): if value is None or value == "": self.OSCOut = None else: if type(value) == tuple: msg = "%s:%d:%s" % (value[0], value[1], value[2]) else: msg = value sep = msg.split(":") host = str(sep[0].strip()) port = int(sep[1].strip()) address = str(sep[2].strip()) self.OSCOut = (host, port, address) def getOpenSndCtrl(self): return self.openSndCtrl def getOSCOut(self): return self.OSCOut def getWithOSC(self): if self.openSndCtrl is not None: return True else: return False def setAutomationData(self, data): # convert values on scaling temp = [] log = self.getLog() minval = self.getMinValue() maxval = self.getMaxValue() automationlength = self.getAutomationLength() frac = automationlength / CeciliaLib.getVar("totalTime") virtuallength = len(data) / frac data.extend([data[-1]] * int(((1 - frac) * virtuallength))) totallength = float(len(data)) oldpos = 0 oldval = data[0] if log: maxOnMin = maxval / minval torec = math.log10(oldval / minval) / math.log10(maxOnMin) else: maxMinusMin = maxval - minval torec = (oldval - minval) / maxMinusMin temp.append([0.0, torec]) for i, val in enumerate(data): length = (i - oldpos) / totallength pos = oldpos / totallength + length if val == 0: val = oldval if log: torec = math.log10(val / minval) / math.log10(maxOnMin) else: torec = (val - minval) / maxMinusMin temp.append([pos, torec]) oldval = val oldpos = i self.automationData = temp def getAutomationData(self): return [[x[0], x[1]] for x in self.automationData] def update(self, val): if not self.slider.HasCapture() and self.getPlay() == 1 or self.getWithMidi(): self.setValue(val) class RangeSlider(wx.Panel): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(200, 20), valtype='float', log=False, function=None, cecslider=None): wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size, style=wx.BORDER_NONE) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.SetMinSize(self.GetSize()) self.sliderHeight = 14 self.borderWidth = 1 self.action = None self.fillcolor = SLIDER_BACK_COLOUR self.knobcolor = SLIDER_KNOB_COLOUR self.handlecolor = wx.Colour(int(self.knobcolor[1:3]) - 10, int(self.knobcolor[3:5]) - 10, int(self.knobcolor[5:7]) - 10) self.outFunction = function self.cecslider = cecslider if valtype.startswith('i'): self.myType = int else: self.myType = float self.log = log self.SetRange(minvalue, maxvalue) self.handles = [minvalue, maxvalue] if init is not None: if type(init) in [list, tuple]: if len(init) == 1: self.SetValue([init[0], init[0]]) else: self.SetValue([init[0], init[1]]) else: self.SetValue([minvalue, maxvalue]) else: self.SetValue([minvalue, maxvalue]) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_RIGHT_DOWN, self.MouseRightDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.Bind(wx.EVT_RIGHT_UP, self.MouseUp) self.Bind(wx.EVT_MOTION, self.MouseMotion) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnResize) def cleanup(self): self.Unbind(wx.EVT_LEFT_DOWN, handler=self.MouseDown) self.Unbind(wx.EVT_RIGHT_DOWN, handler=self.MouseRightDown) self.Unbind(wx.EVT_LEFT_UP, handler=self.MouseUp) self.Unbind(wx.EVT_RIGHT_UP, handler=self.MouseUp) self.Unbind(wx.EVT_MOTION, handler=self.MouseMotion) self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.Unbind(wx.EVT_SIZE, handler=self.OnResize) self.outFunction = None self.cecslider = None def createSliderBitmap(self): w, h = self.GetSize() b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=1)) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR)) dc.DrawRectangle(0, 0, w, h) dc.SetBrush(wx.Brush("#777777")) dc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) h2 = self.sliderHeight // 4 dc.DrawRoundedRectangle(0, h2, w, self.sliderHeight, 4) dc.SelectObject(wx.NullBitmap) b.SetMaskColour("#777777") self.sliderMask = b def setFillColour(self, col1, col2): self.fillcolor = col1 self.knobcolor = col2 self.handlecolor = wx.Colour(self.knobcolor[0] * 0.35, self.knobcolor[1] * 0.35, self.knobcolor[2] * 0.35) self.createBackgroundBitmap() def SetRange(self, minvalue, maxvalue): self.minvalue = minvalue self.maxvalue = maxvalue def scale(self, pos): tmp = [] for p in pos: inter = CeciliaLib.tFromValue(p, 1, self.GetSize()[0] - 1) inter2 = CeciliaLib.interpFloat(inter, self.minvalue, self.maxvalue) tmp.append(inter2) return tmp def MouseRightDown(self, evt): xpos = evt.GetPosition()[0] if xpos > (self.handlePos[0] - 5) and xpos < (self.handlePos[1] + 5): self.lastpos = xpos self.length = self.handlePos[1] - self.handlePos[0] self.action = 'drag' self.handles = self.scale(self.handlePos) self.CaptureMouse() wx.CallAfter(self.Refresh) def MouseDown(self, evt): size = self.GetSize() xpos = evt.GetPosition()[0] self.middle = (self.handlePos[1] - self.handlePos[0]) // 2 + self.handlePos[0] midrec = wx.Rect(self.middle - 7, 4, 15, size[1] - 9) if midrec.Contains(evt.GetPosition()): self.lastpos = xpos self.length = self.handlePos[1] - self.handlePos[0] self.action = 'drag' elif xpos < self.middle: self.handlePos[0] = CeciliaLib.clamp(xpos, 1, self.handlePos[1]) self.action = 'left' elif xpos > self.middle: self.handlePos[1] = CeciliaLib.clamp(xpos, self.handlePos[0], size[0] - 1) self.action = 'right' self.handles = self.scale(self.handlePos) self.CaptureMouse() wx.CallAfter(self.Refresh) def MouseMotion(self, evt): size = self.GetSize() if evt.Dragging() and self.HasCapture() and evt.LeftIsDown() or evt.RightIsDown(): xpos = evt.GetPosition()[0] if self.action == 'drag': off = xpos - self.lastpos self.lastpos = xpos self.handlePos[0] = CeciliaLib.clamp(self.handlePos[0] + off, 1, size[0] - self.length) self.handlePos[1] = CeciliaLib.clamp(self.handlePos[1] + off, self.length, size[0] - 1) if self.action == 'left': self.handlePos[0] = CeciliaLib.clamp(xpos, 1, self.handlePos[1] - 4) elif self.action == 'right': self.handlePos[1] = CeciliaLib.clamp(xpos, self.handlePos[0] + 4, size[0] - 1) self.handles = self.scale(self.handlePos) wx.CallAfter(self.Refresh) def MouseUp(self, evt): if self.HasCapture(): self.ReleaseMouse() if self.cecslider.getUp() and CeciliaLib.getVar("currentModule") is not None: getattr(CeciliaLib.getVar("currentModule"), self.cecslider.name + "_up")(self.GetValue()) def OnResize(self, evt): self.createSliderBitmap() self.createBackgroundBitmap() self.clampHandlePos() wx.CallAfter(self.Refresh) def clampHandlePos(self): size = self.GetSize() tmp = [] for handle in [min(self.handles), max(self.handles)]: pos = CeciliaLib.tFromValue(handle, self.minvalue, self.maxvalue) * size[0] pos = CeciliaLib.clamp(pos, 1, size[0] - 1) tmp.append(pos) self.handlePos = tmp class HRangeSlider(RangeSlider): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(200, 15), valtype='float', log=False, function=None, cecslider=None): RangeSlider.__init__(self, parent, minvalue, maxvalue, init, pos, size, valtype, log, function, cecslider) self.SetMinSize((50, 15)) self.createSliderBitmap() self.createBackgroundBitmap() self.clampHandlePos() self.midictl1 = '' self.midictl2 = '' self.openSndCtrl1 = '' self.openSndCtrl2 = '' self.midiLearn = False self.font = wx.Font(LABEL_FONT - 2, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT) def setSliderHeight(self, height): self.sliderHeight = height self.createSliderBitmap() self.createBackgroundBitmap() wx.CallAfter(self.Refresh) def createBackgroundBitmap(self): w, h = self.GetSize() self.backgroundBitmap = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(self.backgroundBitmap) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=self.borderWidth, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) # Draw inner part h2 = self.sliderHeight // 4 rec = wx.Rect(0, h2, w, self.sliderHeight) dc.GradientFillLinear(rec, GRADIENT_DARK_COLOUR, self.fillcolor, wx.BOTTOM) dc.DrawBitmap(self.sliderMask, 0, 0, True) dc.SelectObject(wx.NullBitmap) def setMidiCtl(self, str1, str2): self.midictl1 = str1 self.midictl2 = str2 self.midiLearn = False def inMidiLearnMode(self): self.midiLearn = True wx.CallAfter(self.Refresh) def setOpenSndCtrl(self, str, side): if side == 'left': self.openSndCtrl1 = str else: self.openSndCtrl2 = str self.OnResize(None) def SetOneValue(self, value, which): value = CeciliaLib.clamp(value, self.minvalue, self.maxvalue) if self.log: t = CeciliaLib.toLog(value, self.minvalue, self.maxvalue) value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) else: t = CeciliaLib.tFromValue(value, self.minvalue, self.maxvalue) value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) if self.myType == int: value = int(value) self.handles[which] = value self.OnResize(None) def SetValue(self, values): tmp = [] for val in values: value = CeciliaLib.clamp(val, self.minvalue, self.maxvalue) if self.log: t = CeciliaLib.toLog(value, self.minvalue, self.maxvalue) value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) else: t = CeciliaLib.tFromValue(value, self.minvalue, self.maxvalue) value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) if self.myType == int: value = int(value) tmp.append(value) self.handles = tmp self.OnResize(None) def GetValue(self): tmp = [] for value in self.handles: if self.log: t = CeciliaLib.tFromValue(value, self.minvalue, self.maxvalue) val = CeciliaLib.toExp(t, self.minvalue, self.maxvalue) else: val = value if self.myType == int: val = int(val) tmp.append(val) tmp = [min(tmp), max(tmp)] return tmp def OnPaint(self, evt): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetFont(self.font) dc.SetTextForeground(LABEL_LABEL_COLOUR) dc.DrawBitmap(self.backgroundBitmap, 0, 0) # Draw handles dc.SetPen(wx.Pen(self.handlecolor, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(self.handlecolor)) if self.handlePos[1] - self.handlePos[0] > 4: rec = wx.Rect(self.handlePos[0], 3, self.handlePos[1] - self.handlePos[0], h - 7) dc.DrawRoundedRectangle(rec, 4) dc.SetPen(wx.Pen(self.fillcolor, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(self.fillcolor)) mid = (self.handlePos[1] - self.handlePos[0]) // 2 + self.handlePos[0] rec = wx.Rect(mid - 4, 4, 8, h - 9) dc.DrawRoundedRectangle(rec, 3) else: mid = (self.handlePos[1] - self.handlePos[0]) // 2 + self.handlePos[0] rec = wx.Rect(mid - 4, 4, 8, h - 9) dc.DrawRoundedRectangle(rec, 3) if self.midiLearn: dc.SetFont(wx.Font(LABEL_FONT - 1, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT)) dc.DrawLabel("Move 2 MIDI controllers...", wx.Rect(5, 0, 50, h), wx.ALIGN_CENTER_VERTICAL) elif self.openSndCtrl1 or self.openSndCtrl2: if self.openSndCtrl1: dc.DrawLabel(self.openSndCtrl1, wx.Rect(5, 0, w, h), wx.ALIGN_CENTER_VERTICAL) if self.openSndCtrl2: textwidth = dc.GetTextExtent(self.openSndCtrl2)[0] + 5 dc.DrawLabel(self.openSndCtrl2, wx.Rect(w - textwidth, 0, textwidth, h), wx.ALIGN_CENTER_VERTICAL) else: textwidth = dc.GetTextExtent(self.midictl2)[0] + 5 dc.DrawLabel(self.midictl1, wx.Rect(5, 0, 30, h), wx.ALIGN_CENTER_VERTICAL) dc.DrawLabel(self.midictl2, wx.Rect(w - textwidth, 0, textwidth, h), wx.ALIGN_CENTER_VERTICAL) # Send value if self.outFunction: self.outFunction(self.GetValue()) class CECRange: def __init__(self, parent, minvalue, maxvalue, init=None, label='range', unit='', valtype='float', log=False, name='', gliss=.025, midictl=None, tooltip='', up=False): self.widget_type = "range" self.parent = parent self.valtype = valtype self.name = name self.gliss = gliss self.automationLength = None self.automationData = [] self.path = os.path.join(AUTOMATION_SAVE_PATH, self.name) self.minvalue = minvalue self.maxvalue = maxvalue self.log = log self.up = up self.convertSliderValue = {'min': 200, 'max': 200} self.midictl = None self.midichan = [1, 1] self.openSndCtrl = None self.OSCOut = None pos = (0, 0) size = (200, 16) self.slider = HRangeSlider(parent, minvalue, maxvalue, init, pos, size, valtype, log, self.writeToEntry, self) self.slider.setSliderHeight(11) self.setMidiCtl(midictl) self.label = Label(parent, label, size=(100, 16), outFunction=self.onLabelClick, dclickFunction=self.onLabelDClick) CeciliaLib.setToolTip(self.label, TT_RANGE_LABEL) self.entryUnit = RangeEntryUnit(parent, self.slider.GetValue(), unit, size=(120, 16), valtype=valtype, outFunction=self.entryReturn) CeciliaLib.setToolTip(self.entryUnit, TT_RANGE_DISPLAY) self.buttons = PlayRecButtons(parent, self, size=(40, 16)) CeciliaLib.setToolTip(self.buttons, TT_SLIDER_AUTO) def cleanup(self): self.entryUnit.cleanup() self.slider.cleanup() self.label.cleanup() self.buttons.cleanup() def refresh(self): wx.CallAfter(self.slider.Refresh) wx.CallAfter(self.label.Refresh) wx.CallAfter(self.entryUnit.Refresh) def setFillColour(self, col1, col2, col3): self.slider.setFillColour(col3, col2) self.label.setBackColour(col1) self.entryUnit.setBackColour(col1) def onLabelClick(self, label, shift=False, alt=False, side='left'): if not self.up: # alt is now the right click rightclick = alt if side == 'left': label = label + ' min' else: label = label + ' max' if rightclick and shift: self.setMidiCtl(None) elif shift: CeciliaLib.getVar("grapher").setShowLineSolo(label) CeciliaLib.getVar("grapher").toolbar.menu.setLabel(label, True) elif rightclick: if CeciliaLib.getVar("useMidi"): CeciliaLib.getVar("grapher").toolbar.menu.setLabel(label, True) CeciliaLib.getVar("audioServer").midiLearn(self, True) self.slider.inMidiLearnMode() else: CeciliaLib.showErrorDialog("Midi not initialized!", "There is no Midi interface connected!") else: CeciliaLib.getVar("grapher").resetShow() CeciliaLib.getVar("grapher").toolbar.menu.setLabel(label, True) def onLabelDClick(self, side='left'): f = OSCPopupFrame(CeciliaLib.getVar('interface'), self, side=side) f.CenterOnParent() f.Show() def setOSCInput(self, value, side): self.setOpenSndCtrl(value, side) def setOSCOutput(self, value, side): self.setOSCOut(value, side) def setConvertSliderValue(self, x, end='min'): self.convertSliderValue[end] = x def setAutomationLength(self, x): self.automationLength = x def getAutomationLength(self): return self.automationLength def sendValue(self, value): if self.getPlay() in [0, 1] or self.getRec() == 1: if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._sliders[self.name].setValue(value) def entryReturn(self, value): self.slider.SetValue(value) self.sendValue(value) def writeToEntry(self, values): tmp = [] if self.slider.myType == float: for value in values: if value >= 10000: val = int(value) elif value >= 1000: val = int(value) elif value >= 100: val = float('%5.1f' % value) elif value >= 10: val = float('%5.1f' % value) elif value >= -10: val = float('%5.2f' % value) elif value >= -100: val = float('%5.1f' % value) elif value >= -1000: val = float('%5.1f' % value) elif value >= -10000: val = float('%5.1f' % value) else: val = float('%5.0f' % value) tmp.append(val) else: tmp = [i for i in values] self.entryUnit.setValue(tmp) self.sendValue(values) def getUp(self): return self.up def setOneValue(self, value, which): self.slider.SetOneValue(value, which) def setValue(self, value): self.slider.SetValue(value) def getValue(self): return self.slider.GetValue() def getLog(self): return self.log def getMinValue(self): return self.minvalue def getMaxValue(self): return self.maxvalue def getName(self): return self.name def setPlay(self, x): self.buttons.setPlay(x) def setRec(self, x): self.buttons.setRec(x) def getPlay(self): return self.buttons.getPlay() def getRec(self): return self.buttons.getRec() def getState(self): return [self.getValue(), self.getPlay(), self.getMidiCtl(), self.getMidiChannel(), self.getOpenSndCtrl(), self.getOSCOut()] def setState(self, values): self.setValue(values[0]) self.setPlay(values[1]) if len(values) >= 4: self.setMidiChannel(values[3]) self.setMidiCtl(values[2]) if len(values) >= 5: if values[4] is not None: for i, tup in enumerate(values[4]): if tup != (): if i == 0: self.setOpenSndCtrl("%d:%s" % (tup[0], tup[1]), 'left') else: self.setOpenSndCtrl("%d:%s" % (tup[0], tup[1]), 'right') else: self.setOpenSndCtrl("", 'left') self.setOpenSndCtrl("", 'right') if len(values) >= 6: if values[5] is not None: for i, tup in enumerate(values[5]): if tup != (): if i == 0: self.setOSCOut("%s:%d:%s" % (tup[0], tup[1], tup[2]), 'left') else: self.setOSCOut("%s:%d:%s" % (tup[0], tup[1], tup[2]), 'right') else: self.setOSCOut("", 'left') self.setOSCOut("", 'right') def getPath(self): return self.path def setMidiCtl(self, ctls): if ctls is None: self.midictl = None self.midichan = [1, 1] self.slider.setMidiCtl('', '') else: self.midictl = ctls self.slider.setMidiCtl("%d:%d" % (self.midictl[0], self.midichan[0]), "%d:%d" % (self.midictl[1], self.midichan[1])) self.openSndCtrl = None self.slider.setOpenSndCtrl('', "left") self.slider.setOpenSndCtrl('', "right") wx.CallAfter(self.slider.Refresh) def getMidiCtl(self): return self.midictl def setMidiChannel(self, chan): self.midichan = chan def getMidiChannel(self): return self.midichan def getWithMidi(self): if self.getMidiCtl() is not None and CeciliaLib.getVar("useMidi"): return True else: return False def setOpenSndCtrl(self, value, side='left'): if value is None or value == "": self.slider.setOpenSndCtrl("", side) if self.openSndCtrl is not None: if side == 'left': self.openSndCtrl = ((), self.openSndCtrl[1]) else: self.openSndCtrl = (self.openSndCtrl[0], ()) if self.openSndCtrl == ((), ()): self.openSndCtrl = None else: if type(value) == tuple: msg = "%s:%s" % (value[0], value[1]) else: msg = value sep = msg.split(":") port = int(sep[0].strip()) address = str(sep[1].strip()) if self.openSndCtrl is None: if side == 'left': self.openSndCtrl = ((port, address), ()) else: self.openSndCtrl = ((), (port, address)) else: if side == 'left': self.openSndCtrl = ((port, address), self.openSndCtrl[1]) else: self.openSndCtrl = (self.openSndCtrl[0], (port, address)) self.slider.setOpenSndCtrl("%d:%s" % (port, address), side) self.setMidiCtl(None) def setOSCOut(self, value, side='left'): if value is not None: if value == "": if self.OSCOut is not None: if side == 'left': self.OSCOut = ((), self.OSCOut[1]) else: self.OSCOut = (self.OSCOut[0], ()) if self.OSCOut == ((), ()): self.OSCOut = None else: sep = value.split(":") host = str(sep[0].strip()) port = int(sep[1].strip()) address = str(sep[2].strip()) if self.OSCOut is None: if side == 'left': self.OSCOut = ((host, port, address), ()) else: self.OSCOut = ((), (host, port, address)) else: if side == 'left': self.OSCOut = ((host, port, address), self.OSCOut[1]) else: self.OSCOut = (self.OSCOut[0], (host, port, address)) def getOpenSndCtrl(self): return self.openSndCtrl def getOSCOut(self): return self.OSCOut def getWithOSC(self): if self.openSndCtrl is not None: return True else: return False def setAutomationData(self, data, which=0): # convert values on scaling temp = [] log = self.getLog() minval = self.getMinValue() maxval = self.getMaxValue() automationlength = self.getAutomationLength() frac = automationlength / CeciliaLib.getVar("totalTime") virtuallength = len(data) / frac data.extend([data[-1]] * int(((1 - frac) * virtuallength))) totallength = float(len(data)) oldpos = 0 oldval = data[0] if log: maxOnMin = maxval / minval torec = math.log10(oldval / minval) / math.log10(maxOnMin) else: maxMinusMin = maxval - minval torec = (oldval - minval) / maxMinusMin temp.append([0.0, torec]) for i, val in enumerate(data): length = (i - oldpos) / totallength pos = oldpos / totallength + length if log: torec = math.log10(val / minval) / math.log10(maxOnMin) else: torec = (val - minval) / maxMinusMin temp.append([pos, torec]) oldval = val oldpos = i if len(self.automationData) < 2: self.automationData.append(temp) else: self.automationData[which] = temp def getAutomationData(self, which=0): return [[x[0], x[1]] for x in self.automationData[which]] def update(self, val): if not self.slider.HasCapture() and self.getPlay() == 1 or self.getWithMidi(): self.setValue(val) class SplitterSlider(wx.Panel): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(200, 20), num_knobs=3, valtype='float', log=False, function=None, cecslider=None): wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size, style=wx.BORDER_NONE) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.SetMinSize(self.GetSize()) self.sliderHeight = 14 self.knobSize = 11 self.borderWidth = 1 self.selectedHandle = None self.handleWidth = 10 self.fillcolor = SLIDER_BACK_COLOUR self.knobcolor = SLIDER_KNOB_COLOUR self.handlecolor = wx.Colour(int(self.knobcolor[1:3]) - 10, int(self.knobcolor[3:5]) - 10, int(self.knobcolor[5:7]) - 10) self.outFunction = function self.cecslider = cecslider self.num_knobs = num_knobs if valtype.startswith('i'): self.myType = int else: self.myType = float self.log = log self.SetRange(minvalue, maxvalue) self.handles = [0 for i in range(self.num_knobs)] if init is not None: if type(init) in [list, tuple]: if len(init) != self.num_knobs: vals = [float(i) / self.num_knobs * (self.maxvalue - self.minvalue) + self.minvalue for i in range(self.num_knobs)] self.SetValue(vals) else: self.SetValue([v for v in init]) else: vals = [float(i) / self.num_knobs * (self.maxvalue - self.minvalue) + self.minvalue for i in range(self.num_knobs)] self.SetValue(vals) else: vals = [float(i) / self.num_knobs * (self.maxvalue - self.minvalue) + self.minvalue for i in range(self.num_knobs)] self.SetValue(vals) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.Bind(wx.EVT_RIGHT_UP, self.MouseUp) self.Bind(wx.EVT_MOTION, self.MouseMotion) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnResize) def cleanup(self): self.Unbind(wx.EVT_LEFT_DOWN, handler=self.MouseDown) self.Unbind(wx.EVT_LEFT_UP, handler=self.MouseUp) self.Unbind(wx.EVT_RIGHT_UP, handler=self.MouseUp) self.Unbind(wx.EVT_MOTION, handler=self.MouseMotion) self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.Unbind(wx.EVT_SIZE, handler=self.OnResize) self.outFunction = None self.cecslider = None def createSliderBitmap(self): w, h = self.GetSize() b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=1)) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR)) dc.DrawRectangle(0, 0, w, h) dc.SetBrush(wx.Brush("#777777")) dc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) h2 = self.sliderHeight // 4 dc.DrawRoundedRectangle(0, h2, w, self.sliderHeight, 4) dc.SelectObject(wx.NullBitmap) b.SetMaskColour("#777777") self.sliderMask = b def createKnobMaskBitmap(self): w, h = self.knobSize, self.GetSize()[1] b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) rec = wx.Rect(0, 0, w, h) dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=1)) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR)) dc.DrawRectangle(rec) h2 = self.sliderHeight // 4 rec = wx.Rect(0, h2, w, self.sliderHeight) dc.GradientFillLinear(rec, GRADIENT_DARK_COLOUR, self.fillcolor, wx.BOTTOM) dc.SetBrush(wx.Brush("#787878")) dc.SetPen(wx.Pen(KNOB_BORDER_COLOUR, width=1)) dc.DrawRoundedRectangle(0, 0, w, h, 2) dc.SelectObject(wx.NullBitmap) b.SetMaskColour("#787878") self.knobMask = b def setFillColour(self, col1, col2): self.fillcolor = col1 self.knobcolor = col2 self.handlecolor = wx.Colour(self.knobcolor[0] * 0.25, self.knobcolor[1] * 0.25, self.knobcolor[2] * 0.25) self.createSliderBitmap() def SetRange(self, minvalue, maxvalue): self.minvalue = minvalue self.maxvalue = maxvalue def scale(self, pos): tmp = [] for p in pos: inter = CeciliaLib.tFromValue(p, 1, self.GetSize()[0] - 1) inter2 = CeciliaLib.interpFloat(inter, self.minvalue, self.maxvalue) tmp.append(inter2) return tmp def setHandlePosition(self, xpos): size = self.GetSize() halfSize = self.handleWidth // 2 + 1 if self.selectedHandle == 0: self.handlePos[self.selectedHandle] = CeciliaLib.clamp(xpos, halfSize, self.handlePos[self.selectedHandle + 1] - self.handleWidth) elif self.selectedHandle == (self.num_knobs - 1): self.handlePos[self.selectedHandle] = CeciliaLib.clamp(xpos, self.handlePos[self.selectedHandle - 1] + self.handleWidth, size[0] - halfSize) else: self.handlePos[self.selectedHandle] = CeciliaLib.clamp(xpos, self.handlePos[self.selectedHandle - 1] + self.handleWidth, self.handlePos[self.selectedHandle + 1] - self.handleWidth) self.handles = self.scale(self.handlePos) def MouseDown(self, evt): w, h = self.GetSize() pos = evt.GetPosition() xpos = pos[0] self.selectedHandle = None for i, handle in enumerate(self.handlePos): rec = wx.Rect(handle - 5, 3, 10, h - 7) if rec.Contains(pos): self.selectedHandle = i break if self.selectedHandle is None: return self.setHandlePosition(xpos) self.CaptureMouse() wx.CallAfter(self.Refresh) def MouseMotion(self, evt): if evt.Dragging() and self.HasCapture() and evt.LeftIsDown(): self.setHandlePosition(evt.GetPosition()[0]) wx.CallAfter(self.Refresh) def MouseUp(self, evt): if self.HasCapture(): self.ReleaseMouse() if self.cecslider.getUp() and CeciliaLib.getVar("currentModule") is not None: getattr(CeciliaLib.getVar("currentModule"), self.cecslider.name + "_up")(self.GetValue()) def OnResize(self, evt): self.createSliderBitmap() self.createBackgroundBitmap() self.clampHandlePos() wx.CallAfter(self.Refresh) def clampHandlePos(self): size = self.GetSize() tmp = [] for handle in self.handles: pos = CeciliaLib.tFromValue(handle, self.minvalue, self.maxvalue) * (size[0]) pos = CeciliaLib.clamp(pos, 1, size[0] - 1) tmp.append(pos) self.handlePos = tmp class HSplitterSlider(SplitterSlider): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(200, 15), num_knobs=3, valtype='float', log=False, function=None, cecslider=None): SplitterSlider.__init__(self, parent, minvalue, maxvalue, init, pos, size, num_knobs, valtype, log, function, cecslider) self.SetMinSize((50, 15)) self.createSliderBitmap() self.createBackgroundBitmap() self.createKnobMaskBitmap() self.createKnobBitmap() self.clampHandlePos() self.midictl1 = '' self.midictl2 = '' self.midiLearn = False self.font = wx.Font(SPLITTER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT) def setSliderHeight(self, height): self.sliderHeight = height self.createSliderBitmap() self.createBackgroundBitmap() wx.CallAfter(self.Refresh) def createBackgroundBitmap(self): w, h = self.GetSize() self.backgroundBitmap = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(self.backgroundBitmap) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=self.borderWidth, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) # Draw inner part h2 = self.sliderHeight // 4 rec = wx.Rect(0, h2, w, self.sliderHeight) dc.GradientFillLinear(rec, GRADIENT_DARK_COLOUR, self.fillcolor, wx.BOTTOM) dc.DrawBitmap(self.sliderMask, 0, 0, True) dc.SelectObject(wx.NullBitmap) def createKnobBitmap(self): w, h = self.GetSize() self.knobBitmap = wx.EmptyBitmap(self.knobSize, h) dc = wx.MemoryDC(self.knobBitmap) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() rec = wx.Rect(0, 0, self.knobSize, h) dc.GradientFillLinear(rec, GRADIENT_DARK_COLOUR, self.handlecolor, wx.RIGHT) dc.DrawBitmap(self.knobMask, rec[0], rec[1], True) dc.SelectObject(wx.NullBitmap) def setMidiCtl(self, str1, str2): self.midictl1 = str1 self.midictl2 = str2 self.midiLearn = False def inMidiLearnMode(self): self.midiLearn = True wx.CallAfter(self.Refresh) def SetValue(self, values): tmp = [] for val in values: value = CeciliaLib.clamp(val, self.minvalue, self.maxvalue) if self.log: t = CeciliaLib.toLog(value, self.minvalue, self.maxvalue) value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) else: t = CeciliaLib.tFromValue(value, self.minvalue, self.maxvalue) value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) if self.myType == int: value = int(value) tmp.append(value) self.handles = tmp self.OnResize(None) def GetValue(self): tmp = [] for value in self.handles: if self.log: t = CeciliaLib.tFromValue(value, self.minvalue, self.maxvalue) val = CeciliaLib.toExp(t, self.minvalue, self.maxvalue) else: val = value if self.myType == int: val = int(val) tmp.append(val) return tmp def OnPaint(self, evt): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetFont(self.font) dc.SetTextForeground(LABEL_LABEL_COLOUR) dc.DrawBitmap(self.backgroundBitmap, 0, 0) # Draw handles dc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(self.handlecolor)) for i, handle in enumerate(self.handlePos): dc.DrawBitmap(self.knobBitmap, handle - 5, 0) rec = wx.Rect(handle - 4, 1, 10, h - 2) dc.DrawLabel(str(i + 1), rec, wx.ALIGN_CENTER) if not self.midiLearn: dc.DrawLabel(self.midictl1, wx.Rect(10, 0, 30, h), wx.ALIGN_CENTER_VERTICAL) dc.DrawLabel(self.midictl2, wx.Rect(w - 20, 0, 20, h), wx.ALIGN_CENTER_VERTICAL) else: dc.SetFont(wx.Font(LABEL_FONT - 1, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT)) dc.DrawLabel("Move 2 MIDI controllers...", wx.Rect(5, 0, 50, h), wx.ALIGN_CENTER_VERTICAL) # Send value if self.outFunction: self.outFunction(self.GetValue()) class CECSplitter: def __init__(self, parent, minvalue, maxvalue, init=None, label='splitter', unit='', valtype='float', num_knobs=3, log=False, name='', gliss=.025, midictl=None, tooltip='', up=False): self.widget_type = "splitter" self.parent = parent self.valtype = valtype self.num_knobs = num_knobs self.name = name self.gliss = gliss self.automationLength = None self.automationData = [] self.path = os.path.join(AUTOMATION_SAVE_PATH, self.name) self.minvalue = minvalue self.maxvalue = maxvalue self.log = log self.up = up self.midictl = None self.midichan = [1 for i in range(num_knobs)] pos = (0, 0) size = (200, 16) self.slider = HSplitterSlider(parent, minvalue, maxvalue, init, pos, size, num_knobs, valtype, log, self.writeToEntry, self) self.slider.setSliderHeight(11) self.setMidiCtl(midictl) self.label = Label(parent, label, size=(100, 16)) CeciliaLib.setToolTip(self.label, TT_SPLITTER_LABEL) self.entryUnit = SplitterEntryUnit(parent, self.slider.GetValue(), unit, size=(120, 16), num=num_knobs, valtype=valtype, outFunction=self.entryReturn) CeciliaLib.setToolTip(self.entryUnit, TT_SPLITTER_DISPLAY) # Buttons are always hidden for csplitter. self.buttons = PlayRecButtons(parent, self, size=(40, 16)) def cleanup(self): self.entryUnit.cleanup() self.slider.cleanup() self.label.cleanup() self.buttons.cleanup() def refresh(self): wx.CallAfter(self.slider.Refresh) wx.CallAfter(self.label.Refresh) wx.CallAfter(self.entryUnit.Refresh) def setFillColour(self, col1, col2, col3): self.slider.setFillColour(col3, col2) self.label.setBackColour(col1) self.entryUnit.setBackColour(col1) def setAutomationLength(self, x): self.automationLength = x def getAutomationLength(self): return self.automationLength def sendValue(self, value): if self.getPlay() in [0, 1] or self.getRec() == 1: if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._sliders[self.name].setValue(value) def entryReturn(self, value): self.slider.SetValue(value) self.sendValue(value) def writeToEntry(self, values): tmp = [] if self.slider.myType == float: for value in values: if value >= 10000: val = float('%5.0f' % value) elif value >= 1000: val = float('%5.1f' % value) elif value >= 100: val = float('%5.2f' % value) elif value >= 10: val = float('%5.3f' % value) elif value >= -100: val = float('%5.3f' % value) elif value >= -1000: val = float('%5.2f' % value) elif value >= -10000: val = float('%5.1f' % value) else: val = float('%5.2f' % value) tmp.append(val) else: tmp = [i for i in values] self.entryUnit.setValue(tmp) self.sendValue(values) def getUp(self): return self.up def setValue(self, value): self.slider.SetValue(value) def getValue(self): return self.slider.GetValue() def getLog(self): return self.log def getMinValue(self): return self.minvalue def getMaxValue(self): return self.maxvalue def getName(self): return self.name def setPlay(self, x): self.buttons.setPlay(x) def setRec(self, x): self.buttons.setRec(x) def getPlay(self): return self.buttons.getPlay() def getRec(self): return self.buttons.getRec() def getState(self): return [self.getValue(), self.getPlay(), self.getMidiCtl(), self.getMidiChannel()] def setState(self, values): self.setValue(values[0]) self.setPlay(values[1]) self.setMidiCtl(values[2]) if len(values) >= 4: self.setMidiChannel(values[3]) def getPath(self): return self.path def setMidiCtl(self, ctls): if ctls is None: self.midictl = None self.midichan = [1, 1] self.slider.setMidiCtl('', '') else: self.midictl = ctls self.slider.setMidiCtl(str(self.midictl[0]), str(self.midictl[1])) self.slider.Refresh() def getMidiCtl(self): return self.midictl def setMidiChannel(self, chan): self.midichan = chan def getMidiChannel(self): return self.midichan def getWithMidi(self): if self.getMidiCtl() is not None and CeciliaLib.getVar("useMidi"): return True else: return False def setAutomationData(self, data, which=0): # convert values on scaling temp = [] log = self.getLog() minval = self.getMinValue() maxval = self.getMaxValue() automationlength = self.getAutomationLength() frac = automationlength / CeciliaLib.getVar("totalTime") virtuallength = len(data) / frac data.extend([data[-1]] * int(((1 - frac) * virtuallength))) totallength = float(len(data)) oldpos = 0 oldval = data[0] if log: maxOnMin = maxval / minval torec = math.log10(oldval / minval) / math.log10(maxOnMin) else: maxMinusMin = maxval - minval torec = (oldval - minval) / maxMinusMin temp.append([0.0, torec]) for i, val in enumerate(data): length = (i - oldpos) / totallength pos = oldpos / totallength + length if log: torec = math.log10(val / minval) / math.log10(maxOnMin) else: torec = (val - minval) / maxMinusMin temp.append([pos, torec]) oldval = val oldpos = i if len(self.automationData) < 2: self.automationData.append(temp) else: self.automationData[which] = temp def getAutomationData(self, which=0): return [[x[0], x[1]] for x in self.automationData[which]] def update(self, val): if not self.slider.HasCapture() and self.getPlay() == 1 or self.getWithMidi(): self.setValue(val) def buildHorizontalSlidersBox(parent, list): mainBox = wx.BoxSizer(wx.VERTICAL) outBox = wx.BoxSizer(wx.VERTICAL) sliders = [] halfcount = 0 for widget in list: if widget['type'] in ['cslider', 'crange', 'csplitter']: name = widget['name'] label = widget['label'] tooltip = widget['help'] mini = widget['min'] maxi = widget['max'] unit = widget['unit'] init = widget['init'] up = widget['up'] midictl = widget['midictl'] half = widget['half'] if midictl is not None and midictl <= -1: midictl = None valtype = widget['res'] if valtype not in ['int', 'float']: CeciliaLib.showErrorDialog('Error when building interface!', "'res' argument choices are 'int' or 'float'. Reset to 'float'.") valtype = 'float' gliss = widget['gliss'] if gliss < 0.0 or up: gliss = 0.0 linlog = widget['rel'] if linlog not in ['lin', 'log']: CeciliaLib.showErrorDialog('Error when building interface!', "'rel' argument choices are 'lin' or 'log'. Reset to 'lin'.") linlog = 'lin' log = {'lin': False, 'log': True}[linlog] if log and mini == 0 or log and maxi == 0: CeciliaLib.showErrorDialog('Error when building interface!', "'min' or 'max' arguments can't be 0 for a logarithmic slider. Reset to 'lin'.") log = False if widget['type'] == 'cslider': sl = CECSlider(parent, mini, maxi, init, label, unit, valtype, log, name, gliss, midictl, tooltip, up, half) elif widget['type'] == 'crange': sl = CECRange(parent, mini, maxi, init, label, unit, valtype, log, name, gliss, midictl, tooltip, up) else: num_knobs = widget['num_knobs'] sl = CECSplitter(parent, mini, maxi, init, label, unit, valtype, num_knobs, log, name, gliss, midictl, tooltip, up) if up or widget['type'] == "csplitter": sl.buttons.Hide() if not half: box = wx.FlexGridSizer(1, 4, 2, 5) box.AddGrowableCol(2) box.AddMany([(sl.label, 0, wx.LEFT, 5), (sl.buttons, 0, wx.LEFT, 0), (sl.slider, 0, wx.EXPAND), (sl.entryUnit, 0, wx.LEFT | wx.RIGHT, 5)]) mainBox.Add(box, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 2) else: if halfcount % 2 == 0: lowerBox = wx.BoxSizer(wx.HORIZONTAL) leftbox, rightbox = wx.FlexGridSizer(1, 4, 2, 5), wx.FlexGridSizer(1, 4, 2, 5) leftbox.AddGrowableCol(2) rightbox.AddGrowableCol(2) leftbox.AddMany([(sl.label, 0, wx.LEFT, 5), (sl.buttons, 0, wx.LEFT, 0), (sl.slider, 0, wx.EXPAND), (sl.entryUnit, 0, wx.LEFT | wx.RIGHT, 5)]) lowerBox.Add(leftbox, 1, wx.TOP | wx.BOTTOM, 0) mainBox.Add(lowerBox, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 2) else: rightbox.AddMany([(sl.label, 0, wx.LEFT, 5), (sl.buttons, 0, wx.LEFT, 0), (sl.slider, 0, wx.EXPAND), (sl.entryUnit, 0, wx.LEFT | wx.RIGHT, 5)]) lowerBox.Add(rightbox, 1, wx.TOP | wx.BOTTOM, 0) halfcount += 1 sliders.append(sl) outBox.Add(mainBox, 0, wx.ALL | wx.EXPAND, 3) return outBox, sliderscecilia5-5.4.1/Resources/TogglePopup.py000066400000000000000000000320461372272363700200110ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ from __future__ import division import wx, random from .Widgets import Label, CustomMenu, Toggle, Button, ListEntry from .constants import * import Resources.CeciliaLib as CeciliaLib class CECPopup: def __init__(self, parent, label, values, init, rate, name, colour, tooltip=True, output=True): self.type = "popup" self.name = name self.output = output self.rate = rate self.label = Label(parent, label, colour=colour[0]) self.popup = CustomMenu(parent, values, init, size=(100, 20), outFunction=self.onPopup, colour=colour[1]) if tooltip: CeciliaLib.setToolTip(self.label, TT_POPUP) def getName(self): return self.name def getValue(self): return self.popup.getIndex() def getFullValue(self): return self.popup.getIndex(), self.popup.getLabel() def getLabel(self): return self.popup.getLabel() def setValue(self, value, out=False): self.popup.setByIndex(value, out) def onPopup(self, value, label): if CeciliaLib.getVar("currentModule") is not None and self.output and self.rate == "k": getattr(CeciliaLib.getVar("currentModule"), self.name)(value, label) class CECToggle: def __init__(self, parent, label, init, rate, name, colour, tooltip, stack=False, output=True): self.type = "toggle" self.name = name self.rate = rate self.output = output if label != '': if stack: self.label = Label(parent, label, colour=colour[0], size=(210, 20)) else: self.label = Label(parent, label, colour=colour[0], size=(100, 20)) CeciliaLib.setToolTip(self.label, TT_TOGGLE) self.toggle = Toggle(parent, init, outFunction=self.onToggle, colour=colour[1]) def getName(self): return self.name def getValue(self): return self.toggle.getValue() def setValue(self, state, dump=None): self.toggle.setValue(state) def onToggle(self, value): if CeciliaLib.getVar("currentModule") is not None and self.output: getattr(CeciliaLib.getVar("currentModule"), self.name)(value) class CECButton: def __init__(self, parent, label, name, colour, tooltip): self.type = "button" self.name = name self.label = Label(parent, label, colour=colour[0]) self.button = Button(parent, outFunction=self.onButton, colour=(colour[1], colour[0])) CeciliaLib.setToolTip(self.label, TT_BUTTON) def getValue(self): return 0 def getName(self): return self.name def onButton(self, value): if CeciliaLib.getVar("currentModule") is not None: getattr(CeciliaLib.getVar("currentModule"), self.name)(value) class CECGen: def __init__(self, parent, label, init, rate, name, popup, colour, tooltip): self.type = "gen" self.name = name self.rate = rate self.popup = popup self.label = Label(parent, label, colour=colour[0]) self.entry = ListEntry(parent, ", ".join([str(x) for x in init]), colour=colour[1], outFunction=self.onEntry) CeciliaLib.setToolTip(self.label, TT_GEN) def getName(self): return self.name def convertToList(self, value): if ", " in value: value = value.split(', ') else: value = value.split(" ") value = [eval(val) for val in value if val.strip() != ""] return value def getValue(self): return self.convertToList(self.entry.getValue()) def setValue(self, value, dump=None): self.entry.setValue(value) def onEntry(self, value): if type(value) != list: value = self.convertToList(value) if CeciliaLib.getVar("currentModule") is not None and self.rate == "k": getattr(CeciliaLib.getVar("currentModule"), self.name)(value) if self.popup is not None: self.popup[0].setValue(self.popup[1], True) class CECPoly: def __init__(self, parent, label, name, values, init, colour, tooltip): self.name = name self.up = 1 popupLabel = label.capitalize() + ' Voices' self.popup = CECPopup(parent, popupLabel, values, init, "i", self.name + 'num', colour, tooltip=False, output=False) chordLabel = label.capitalize() + ' Chords' self.chord = CECPopup(parent, chordLabel, sorted(POLY_CHORDS.keys()), '00 - None', "i", self.name, colour, tooltip=False, output=False) CeciliaLib.setToolTip(self.popup.label, TT_POLY_LABEL) CeciliaLib.setToolTip(self.chord.label, TT_POLY_CHORD) def getValue(self): return self.popup.getValue() def getChordValue(self): return self.chord.getValue() def getUp(self): return self.up class SamplerPopup: def __init__(self, parent, values, init, name, outFunction=None): self.name = name + 'loopi' self.outFunction = outFunction self.value = values.index(init) self.popup = CustomMenu(parent, values, init, size=(100, 20), outFunction=self.onPopup) def onPopup(self, ind, label): self.value = ind if self.outFunction is not None: self.outFunction(self.value) def getName(self): return self.name def getValue(self): return self.value class SamplerToggle: def __init__(self, parent, init, name): self.name = name + 'startpoint' self.value = init self.toggle = Toggle(parent, init, outFunction=self.onToggle) def onToggle(self, val): self.value = val def getName(self): return self.name def getValue(self): return self.value def setValue(self, value): self.value = value self.toggle.setValue(self.value) def buildTogglePopupBox(parent, list): mainBox = wx.BoxSizer(wx.VERTICAL) outBox = wx.BoxSizer(wx.VERTICAL) objects = [] widgetlist = [widget for widget in list if widget['type'] in ['cpopup', 'ctoggle', 'cbutton']] widgetCecList = [widget for widget in list if widget['type'] == 'cgen'] widgetpoly = [widget for widget in list if widget['type'] == 'cpoly'] for i, widget in enumerate(widgetlist): if widget['type'] == 'cpopup': tooltip = widget.get('help', '') name = widget['name'] label = widget.get('label', '') values = widget.get('value') init = widget.get('init', values[0]) rate = widget.get('rate', 'k') if rate == 'k': col = widget.get('col', '') if col == '': col = random.choice(list(COLOUR_CLASSES.keys())) elif col not in COLOUR_CLASSES.keys(): CeciliaLib.showErrorDialog('Wrong colour!', '"%s"\n\nAvailable colours for -col flag are:\n\n%s.' % (col, ', '.join(COLOUR_CLASSES.keys()))) col = random.choice(list(COLOUR_CLASSES.keys())) colour = CeciliaLib.chooseColourFromName(col) else: colour = CeciliaLib.chooseColourFromName("grey") cpopup = CECPopup(parent, label, values, init, rate, name, colour) box = wx.FlexGridSizer(1, 2, 2, 10) box.AddMany([(cpopup.label, 0, wx.TOP | wx.ALIGN_RIGHT, 2), (cpopup.popup, 0, wx.TOP | wx.ALIGN_LEFT, 2)]) mainBox.Add(box, 0, wx.TOP | wx.BOTTOM, 1) objects.append(cpopup) elif widget['type'] == 'ctoggle': tooltip = widget.get('help', '') name = widget['name'] label = widget.get('label', '') stack = widget.get('stack', False) init = widget.get('init', 0) rate = widget.get('rate', 'k') if rate == 'k': col = widget.get('col', '') if col == '': col = random.choice(list(COLOUR_CLASSES.keys())) elif col not in COLOUR_CLASSES.keys(): CeciliaLib.showErrorDialog('Wrong colour!', '"%s"\n\nAvailable colours for -col flag are:\n\n%s.' % (col, ', '.join(COLOUR_CLASSES.keys()))) col = random.choice(list(COLOUR_CLASSES.keys())) colour = CeciliaLib.chooseColourFromName(col) else: colour = CeciliaLib.chooseColourFromName("grey") ctoggle = CECToggle(parent, label, init, rate, name, colour, tooltip, stack) if stack and label != '': labelBox = wx.FlexGridSizer(1, 1, 2, 10) labelBox.Add(ctoggle.label, 0, wx.EXPAND | wx.TOP, 2) mainBox.Add(labelBox, 0, wx.TOP | wx.BOTTOM, 1) stackBox = wx.FlexGridSizer(1, 8, 2, 7) stackBox.Add(ctoggle.toggle, 0, wx.TOP, 2) mainBox.Add(stackBox, 0, wx.TOP | wx.BOTTOM, 1) elif stack: stackBox.Add(ctoggle.toggle, 0, wx.TOP | wx.ALIGN_LEFT, 2) else: box = wx.FlexGridSizer(1, 2, 2, 10) box.AddMany([(ctoggle.label, 0, wx.TOP | wx.ALIGN_RIGHT, 2), (ctoggle.toggle, 0, wx.TOP | wx.ALIGN_LEFT, 2)]) mainBox.Add(box, 0, wx.TOP | wx.BOTTOM, 1) objects.append(ctoggle) elif widget['type'] == 'cbutton': tooltip = widget.get('help', '') name = widget['name'] label = widget.get('label', '') col = widget.get('col', '') if col == '': col = random.choice(list(COLOUR_CLASSES.keys())) elif col not in COLOUR_CLASSES.keys(): CeciliaLib.showErrorDialog('Wrong colour!', '"%s"\n\nAvailable colours for -col flag are:\n\n%s.' % (col, ', '.join(COLOUR_CLASSES.keys()))) col = random.choice(list(COLOUR_CLASSES.keys())) colour = CeciliaLib.chooseColourFromName(col) cbutton = CECButton(parent, label, name, colour, tooltip) box = wx.FlexGridSizer(1, 2, 2, 10) box.AddMany([(cbutton.label, 0, wx.TOP | wx.ALIGN_RIGHT, 2), (cbutton.button, 0, wx.TOP | wx.ALIGN_LEFT, 2)]) mainBox.Add(box, 0, wx.TOP | wx.BOTTOM, 1) objects.append(cbutton) for i, widget in enumerate(widgetCecList): tooltip = widget.get('help', '') name = widget['name'] init = widget.get('init', '1') label = widget.get('label', '') rate = widget.get('rate', 'k') if rate == 'k': col = widget.get('col', '') if col == '': col = random.choice(list(COLOUR_CLASSES.keys())) elif col not in COLOUR_CLASSES.keys(): CeciliaLib.showErrorDialog('Wrong colour!', '"%s"\n\nAvailable colours for -col flag are:\n\n%s.' % (col, ', '.join(COLOUR_CLASSES.keys()))) col = random.choice(list(COLOUR_CLASSES.keys())) colour = CeciliaLib.chooseColourFromName(col) else: colour = CeciliaLib.chooseColourFromName("grey") popup = widget.get("popup", None) ok = False if popup is not None: for obj in objects: if obj.name == popup[0]: popup = (obj, popup[1]) ok = True break if not ok: popup = None clist = CECGen(parent, label, init, rate, name, popup, colour, tooltip) box = wx.FlexGridSizer(1, 2, 2, 10) box.AddMany([(clist.label, 0, wx.TOP | wx.ALIGN_RIGHT, 2), (clist.entry, 0, wx.ALIGN_LEFT | wx.TOP, 2)]) mainBox.Add(box, 0, wx.TOP | wx.BOTTOM, 1) objects.append(clist) for i, widget in enumerate(widgetpoly): tooltip = widget.get('help', '') name = widget['name'] minvoices = widget.get('min', 1) maxvoices = widget.get('max', 10) values = [str(voice) for voice in range(minvoices, maxvoices + 1)] init = widget.get('init', values[0]) label = widget.get('label', '') colour = [CPOLY_COLOUR, CPOLY_COLOUR] cpoly = CECPoly(parent, label, name, values, init, colour, tooltip) box = wx.FlexGridSizer(0, 2, 2, 10) box.AddMany([(cpoly.popup.label, 0, wx.TOP | wx.ALIGN_RIGHT, 2), (cpoly.popup.popup, 0, wx.ALIGN_LEFT | wx.TOP, 2), (cpoly.chord.label, 0, wx.TOP | wx.ALIGN_RIGHT, 2), (cpoly.chord.popup, 0, wx.ALIGN_LEFT | wx.TOP, 2)]) mainBox.Add(box, 0, wx.TOP | wx.BOTTOM, 1) objects.append(cpoly.popup) objects.append(cpoly.chord) outBox.Add(mainBox, 0, wx.ALL, 5) return outBox, objects cecilia5-5.4.1/Resources/Variables.py000066400000000000000000000356461372272363700174650ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import sys, os, wx import unicodedata from .constants import * from pyo import pa_get_default_devices_from_host if sys.version_info[0] < 3: unicode_t = unicode else: unicode_t = str CeciliaVar = dict() CeciliaVar['DEBUG'] = 0 # System variables CeciliaVar['systemPlatform'] = sys.platform CeciliaVar['numDisplays'] = 1 CeciliaVar['displaySize'] = [] CeciliaVar['displayOffset'] = [] CeciliaVar['mainFrame'] = None # Path of the currently opened file CeciliaVar['currentCeciliaFile'] = '' CeciliaVar['lastCeciliaFile'] = '' CeciliaVar['builtinModule'] = False CeciliaVar['currentModuleRef'] = None CeciliaVar['currentModule'] = None CeciliaVar['currentModuleName'] = None # Save path for the various dialogs that pops CeciliaVar['openFilePath'] = os.path.expanduser('~') CeciliaVar['saveFilePath'] = os.path.expanduser('~') CeciliaVar['openAudioFilePath'] = os.path.expanduser('~') CeciliaVar['saveAudioFilePath'] = os.path.expanduser('~') CeciliaVar['grapherLinePath'] = os.path.expanduser('~') # Boolean that says if file was modified since last save CeciliaVar['presetToLoad'] = None # Boolean that says if the grapher (or other UI window ca grab the focus CeciliaVar['canGrabFocus'] = True # Audio / Midi CeciliaVar['availableAudioOutputs'] = [] CeciliaVar['availableAudioOutputIndexes'] = [] CeciliaVar['availableAudioInputs'] = [] CeciliaVar['availableAudioInputIndexes'] = [] CeciliaVar['availableMidiOutputs'] = [] CeciliaVar['availableMidiOutputIndexes'] = [] CeciliaVar['availableMidiInputs'] = [] CeciliaVar['availableMidiInputIndexes'] = [] # Preferences variables CeciliaVar['soundfilePlayer'] = '' CeciliaVar['soundfileEditor'] = '' CeciliaVar['textEditor'] = '' CeciliaVar['prefferedPath'] = '' CeciliaVar['rememberedSound'] = True # Visual variables CeciliaVar['useTooltips'] = 1 CeciliaVar['graphTexture'] = 1 CeciliaVar['moduleDescription'] = '' CeciliaVar['interfaceWidgets'] = [] CeciliaVar['interface'] = None CeciliaVar['interfaceSize'] = (1000, 600) CeciliaVar['interfacePosition'] = (0, 25) CeciliaVar['grapher'] = None CeciliaVar['gainSlider'] = None CeciliaVar['plugins'] = [None] * NUM_OF_PLUGINS CeciliaVar['userSliders'] = [] CeciliaVar['userTogglePopups'] = [] CeciliaVar['userSamplers'] = [] CeciliaVar['userInputs'] = dict() CeciliaVar['samplerSliders'] = [] CeciliaVar['samplerTogglePopup'] = [] CeciliaVar['initPreset'] = None CeciliaVar['presetPanel'] = None CeciliaVar['tooltips'] = [] # Performance variables CeciliaVar['toDac'] = True CeciliaVar['outputFile'] = '' CeciliaVar['totalTime'] = 30.0 CeciliaVar['defaultTotalTime'] = 30.0 CeciliaVar['startOffset'] = 0.0 CeciliaVar['globalFade'] = 0.005 CeciliaVar['audioServer'] = None CeciliaVar['automaticMidiBinding'] = 0 CeciliaVar['showSpectrum'] = 0 CeciliaVar['spectrumFrame'] = None if sys.platform.startswith("win"): def_in, def_out = pa_get_default_devices_from_host("directsound") elif sys.platform.startswith("darwin"): def_in, def_out = pa_get_default_devices_from_host("core") else: def_in, def_out = pa_get_default_devices_from_host("alsa") if def_in < 0: def_in = 0 if def_out < 0: def_out = 0 # Server Flags CeciliaVar['sr'] = 44100 CeciliaVar['nchnls'] = 2 CeciliaVar['defaultNchnls'] = 2 CeciliaVar['sampSize'] = 0 CeciliaVar['audioFileType'] = 'aif' # aif, wav, flac, ogg, sd2, au, caf CeciliaVar['samplePrecision'] = '32 bit' # '32 bit', '64 bit' CeciliaVar['bufferSize'] = '512' CeciliaVar['audioHostAPI'] = 'portaudio' CeciliaVar['audioOutput'] = def_out CeciliaVar['audioInput'] = def_in CeciliaVar['enableAudioInput'] = 0 CeciliaVar['useMidi'] = 0 CeciliaVar['useSoundDur'] = 0 CeciliaVar['midiPort'] = 'portmidi' CeciliaVar['midiDeviceIn'] = 0 CeciliaVar['defaultFirstInput'] = 0 CeciliaVar['defaultFirstOutput'] = 0 CeciliaVar['jack'] = {'client': 'cecilia5'} CeciliaVar['lastAudioFiles'] = "" def loadBitmaps(): CeciliaVar['ICON_VUMETER'] = ICON_VUMETER.GetBitmap() CeciliaVar['ICON_VUMETER_DARK'] = ICON_VUMETER_DARK.GetBitmap() CeciliaVar['ICON_PLUGINS_KNOB'] = ICON_PLUGINS_KNOB.GetBitmap() CeciliaVar['ICON_PLUGINS_KNOB_DISABLE'] = ICON_PLUGINS_KNOB_DISABLE.GetBitmap() CeciliaVar['ICON_PLUGINS_ARROW_UP'] = ICON_PLUGINS_ARROW_UP.GetBitmap() CeciliaVar['ICON_PLUGINS_ARROW_UP_HOVER'] = ICON_PLUGINS_ARROW_UP_HOVER.GetBitmap() CeciliaVar['ICON_PLUGINS_ARROW_DOWN'] = ICON_PLUGINS_ARROW_DOWN.GetBitmap() CeciliaVar['ICON_PLUGINS_ARROW_DOWN_HOVER'] = ICON_PLUGINS_ARROW_DOWN_HOVER.GetBitmap() CeciliaVar['ICON_TB_LOAD'] = ICON_TB_LOAD.GetBitmap() CeciliaVar['ICON_TB_LOAD_OVER'] = ICON_TB_LOAD_OVER.GetBitmap() CeciliaVar['ICON_TB_SAVE'] = ICON_TB_SAVE.GetBitmap() CeciliaVar['ICON_TB_SAVE_OVER'] = ICON_TB_SAVE_OVER.GetBitmap() CeciliaVar['ICON_TB_RESET'] = ICON_TB_RESET.GetBitmap() CeciliaVar['ICON_TB_RESET_OVER'] = ICON_TB_RESET_OVER.GetBitmap() CeciliaVar['ICON_TB_SHOW'] = ICON_TB_SHOW.GetBitmap() CeciliaVar['ICON_TB_SHOW_OVER'] = ICON_TB_SHOW_OVER.GetBitmap() CeciliaVar['ICON_TB_HIDE'] = ICON_TB_HIDE.GetBitmap() CeciliaVar['ICON_TB_HIDE_OVER'] = ICON_TB_HIDE_OVER.GetBitmap() CeciliaVar['ICON_TB_RECYCLE'] = ICON_TB_RECYCLE.GetBitmap() CeciliaVar['ICON_TB_RECYCLE_OVER'] = ICON_TB_RECYCLE_OVER.GetBitmap() CeciliaVar['ICON_TB_PLAY'] = ICON_TB_PLAY.GetBitmap() CeciliaVar['ICON_TB_PLAY_OVER'] = ICON_TB_PLAY_OVER.GetBitmap() CeciliaVar['ICON_TB_EDIT'] = ICON_TB_EDIT.GetBitmap() CeciliaVar['ICON_TB_EDIT_OVER'] = ICON_TB_EDIT_OVER.GetBitmap() CeciliaVar['ICON_TB_OPEN'] = ICON_TB_OPEN.GetBitmap() CeciliaVar['ICON_TB_OPEN_OVER'] = ICON_TB_OPEN_OVER.GetBitmap() CeciliaVar['ICON_TB_CLOSE'] = ICON_TB_CLOSE.GetBitmap() CeciliaVar['ICON_TB_CLOSE_OVER'] = ICON_TB_CLOSE_OVER.GetBitmap() CeciliaVar['ICON_TB_TIME'] = ICON_TB_TIME.GetBitmap() CeciliaVar['ICON_TB_TIME_OVER'] = ICON_TB_TIME_OVER.GetBitmap() CeciliaVar['ICON_TB_DELETE'] = ICON_TB_DELETE.GetBitmap() CeciliaVar['ICON_TB_DELETE_OVER'] = ICON_TB_DELETE_OVER.GetBitmap() CeciliaVar['ICON_RTB_POINTER'] = ICON_RTB_POINTER.GetBitmap() CeciliaVar['ICON_RTB_POINTER_OVER'] = ICON_RTB_POINTER_OVER.GetBitmap() CeciliaVar['ICON_RTB_POINTER_CLICK'] = ICON_RTB_POINTER_CLICK.GetBitmap() CeciliaVar['ICON_RTB_PENCIL'] = ICON_RTB_PENCIL.GetBitmap() CeciliaVar['ICON_RTB_PENCIL_OVER'] = ICON_RTB_PENCIL_OVER.GetBitmap() CeciliaVar['ICON_RTB_PENCIL_CLICK'] = ICON_RTB_PENCIL_CLICK.GetBitmap() CeciliaVar['ICON_RTB_ZOOM'] = ICON_RTB_ZOOM.GetBitmap() CeciliaVar['ICON_RTB_ZOOM_OVER'] = ICON_RTB_ZOOM_OVER.GetBitmap() CeciliaVar['ICON_RTB_ZOOM_CLICK'] = ICON_RTB_ZOOM_CLICK.GetBitmap() CeciliaVar['ICON_RTB_HAND'] = ICON_RTB_HAND.GetBitmap() CeciliaVar['ICON_RTB_HAND_OVER'] = ICON_RTB_HAND_OVER.GetBitmap() CeciliaVar['ICON_RTB_HAND_CLICK'] = ICON_RTB_HAND_CLICK.GetBitmap() CeciliaVar['ICON_PREF_AUDIO'] = ICON_PREF_AUDIO.GetBitmap() CeciliaVar['ICON_PREF_AUDIO_OVER'] = ICON_PREF_AUDIO_OVER.GetBitmap() CeciliaVar['ICON_PREF_AUDIO_CLICK'] = ICON_PREF_AUDIO_CLICK.GetBitmap() CeciliaVar['ICON_PREF_CECILIA'] = ICON_PREF_CECILIA.GetBitmap() CeciliaVar['ICON_PREF_CECILIA_OVER'] = ICON_PREF_CECILIA_OVER.GetBitmap() CeciliaVar['ICON_PREF_CECILIA_CLICK'] = ICON_PREF_CECILIA_CLICK.GetBitmap() CeciliaVar['ICON_PREF_FILER'] = ICON_PREF_FILER.GetBitmap() CeciliaVar['ICON_PREF_FILER_OVER'] = ICON_PREF_FILER_OVER.GetBitmap() CeciliaVar['ICON_PREF_FILER_CLICK'] = ICON_PREF_FILER_CLICK.GetBitmap() CeciliaVar['ICON_PREF_PATH'] = ICON_PREF_PATH.GetBitmap() CeciliaVar['ICON_PREF_PATH_OVER'] = ICON_PREF_PATH_OVER.GetBitmap() CeciliaVar['ICON_PREF_PATH_CLICK'] = ICON_PREF_PATH_CLICK.GetBitmap() CeciliaVar['ICON_PREF_MIDI'] = ICON_PREF_MIDI.GetBitmap() CeciliaVar['ICON_PREF_MIDI_OVER'] = ICON_PREF_MIDI_OVER.GetBitmap() CeciliaVar['ICON_PREF_MIDI_CLICK'] = ICON_PREF_MIDI_CLICK.GetBitmap() CeciliaVar['ICON_PTB_PROCESS'] = ICON_PTB_PROCESS.GetBitmap() CeciliaVar['ICON_PTB_PROCESS_OVER'] = ICON_PTB_PROCESS_OVER.GetBitmap() CeciliaVar['ICON_PTB_RANDOM'] = ICON_PTB_RANDOM.GetBitmap() CeciliaVar['ICON_PTB_RANDOM_OVER'] = ICON_PTB_RANDOM_OVER.GetBitmap() CeciliaVar['ICON_PTB_WAVES'] = ICON_PTB_WAVES.GetBitmap() CeciliaVar['ICON_PTB_WAVES_OVER'] = ICON_PTB_WAVES_OVER.GetBitmap() CeciliaVar['ICON_INPUT_1_FILE'] = ICON_INPUT_1_FILE.GetBitmap() CeciliaVar['ICON_INPUT_2_LIVE'] = ICON_INPUT_2_LIVE.GetBitmap() CeciliaVar['ICON_INPUT_3_MIC'] = ICON_INPUT_3_MIC.GetBitmap() CeciliaVar['ICON_INPUT_4_MIC_RECIRC'] = ICON_INPUT_4_MIC_RECIRC.GetBitmap() CeciliaVar['ICON_XFADE_LINEAR'] = ICON_XFADE_LINEAR.GetBitmap() CeciliaVar['ICON_XFADE_POWER'] = ICON_XFADE_POWER.GetBitmap() CeciliaVar['ICON_XFADE_SIGMOID'] = ICON_XFADE_SIGMOID.GetBitmap() CeciliaVar['ICON_MARIO1'] = ICON_MARIO1.GetBitmap() CeciliaVar['ICON_MARIO2'] = ICON_MARIO2.GetBitmap() CeciliaVar['ICON_MARIO3'] = ICON_MARIO3.GetBitmap() CeciliaVar['ICON_MARIO4'] = ICON_MARIO4.GetBitmap() CeciliaVar['ICON_MARIO5'] = ICON_MARIO5.GetBitmap() CeciliaVar['ICON_MARIO6'] = ICON_MARIO6.GetBitmap() CeciliaVar['ICON_GRAPHER_BACKGROUND'] = ICON_GRAPHER_BACKGROUND.GetBitmap() CeciliaVar['ICON_CECILIA_ABOUT_SMALL'] = ICON_CECILIA_ABOUT_SMALL.GetBitmap() CeciliaVar['ICON_DOC_PREVIOUS'] = ICON_DOC_PREVIOUS.GetBitmap() CeciliaVar['ICON_DOC_NEXT'] = ICON_DOC_NEXT.GetBitmap() CeciliaVar['ICON_DOC_UP'] = ICON_DOC_UP.GetBitmap() def ensureNFD(unistr): if sys.platform.startswith('linux') or sys.platform == 'win32': encodings = [DEFAULT_ENCODING, ENCODING, 'cp1252', 'iso-8859-1', 'utf-16'] format = 'NFC' else: encodings = [DEFAULT_ENCODING, ENCODING, 'macroman', 'iso-8859-1', 'utf-16'] format = 'NFC' decstr = unistr if type(decstr) != unicode_t: for encoding in encodings: try: decstr = decstr.decode(encoding) break except UnicodeDecodeError: continue except: decstr = "UnableToDecodeString" print("Unicode encoding not in a recognized format...") break if decstr == "UnableToDecodeString": return unistr else: return unicodedata.normalize(format, decstr) # Reading and writing of preferences should be moved to CeciliaLib. def readCeciliaPrefsFromFile(): if os.path.isfile(PREFERENCES_FILE): try: file = open(PREFERENCES_FILE, 'rt', encoding=FILE_ENCODING) except: print('Unable to open the preferences file.\n') print('Cecilia will use the default preferences.\n') return print('Loading Cecilia Preferences...') #### Some special cases #### convertToInt = ['sr', 'defaultNchnls', 'audioOutput', 'audioInput', 'sampSize', 'automaticMidiBinding', 'midiDeviceIn', 'useTooltips', 'enableAudioInput', 'graphTexture', 'showSpectrum', 'useSoundDur', 'defaultFirstInput', 'defaultFirstOutput'] convertToFloat = ['defaultTotalTime', 'globalFade', 'DEBUG'] convertToTuple = ['interfaceSize', 'interfacePosition'] jackPrefs = ['client'] text = ensureNFD(file.read()) file.close() # Go through the text file to assign values to the variables try: for i, line in enumerate(text.splitlines()): if i == 0: if not line.startswith("version"): print('preferences file from an older version not used. New preferences will be created.\n') return else: if line.strip(' \n').split('=')[1] != APP_VERSION: print('preferences file from an older version not used. New preferences will be created.\n') return else: continue pref = line.strip(' \n').split('=') if pref[1] != '': if pref[0] in convertToInt: CeciliaVar[pref[0]] = int(pref[1]) elif pref[0] in convertToFloat: CeciliaVar[pref[0]] = float(pref[1]) elif pref[0] in convertToTuple: CeciliaVar[pref[0]] = eval(pref[1]) elif pref[0] in jackPrefs: CeciliaVar['jack'][pref[0]] = pref[1] else: if pref[0] == 'audioHostAPI' and pref[1] not in AUDIO_DRIVERS: CeciliaVar[pref[0]] = 'portaudio' else: CeciliaVar[pref[0]] = pref[1] except: print("something wrong happened when reading preferences " "(probably character encoding/decoding problem), " "some preferences may be lost.") CeciliaVar["nchnls"] = CeciliaVar["defaultNchnls"] else: print('Preferences file not found. Using defaults...\n') def writeCeciliaPrefsToFile(): varsToSave = ['interfaceSize', 'interfacePosition', 'useTooltips', 'enableAudioInput', 'textEditor', 'sr', 'defaultNchnls', 'sampSize', 'audioHostAPI', 'audioFileType', 'audioOutput', 'audioInput', 'midiPort', 'midiDeviceIn', 'samplePrecision', 'client', 'graphTexture', 'globalFade', 'bufferSize', 'soundfilePlayer', 'soundfileEditor', 'prefferedPath', 'DEBUG', 'openFilePath', 'saveFilePath', 'saveAudioFilePath', 'openAudioFilePath', 'grapherLinePath', 'defaultTotalTime', 'lastAudioFiles', 'automaticMidiBinding', 'showSpectrum', 'useSoundDur', 'defaultFirstInput', 'defaultFirstOutput', 'lastCeciliaFile'] print('Writing Cecilia preferences...') try: file = open(PREFERENCES_FILE, 'wt') except IOError: print('Unable to open the preferences file.\n') return file.write("version=%s\n" % APP_VERSION) for key in CeciliaVar: if key in varsToSave: line = '%s=%s\n' % (key, CeciliaVar[key]) file.write(line) elif key == 'jack': line = '%s=%s\n' % ('client', CeciliaVar[key]['client']) file.write(line) file.close() readCeciliaPrefsFromFile() cecilia5-5.4.1/Resources/Widgets.py000066400000000000000000005373331372272363700171630ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx, math, os, random, webbrowser import wx.richtext as rt from pyo import PyoGuiSpectrum, rescale from pyo.lib._wxwidgets import ControlSlider, HRangeSlider from .constants import * from .Drunk import * import Resources.CeciliaLib as CeciliaLib class MenuFrame(wx.Menu): def __init__(self, parent, choice): wx.Menu.__init__(self) self.parent = parent for c in choice: item = wx.MenuItem(self, wx.ID_ANY, c) self.Append(item) self.Bind(wx.EVT_MENU, self.onChoose, id=item.GetId()) def onChoose(self, event): id = event.GetId() item = self.FindItemById(id) obj = item.GetItemLabelText() self.parent.setLabel(obj, True) #--------------------------- # Popup menu # outFunction return (index, value as string) # -------------------------- class CustomMenu(wx.Panel): def __init__(self, parent, choice=[], init='', size=(100, 20), outFunction=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundColour(BACKGROUND_COLOUR) self._backgroundColour = BACKGROUND_COLOUR self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self._enable = True self.outFunction = outFunction self.choice = [str(choice) for choice in choice] if str(init) in self.choice: self.setLabel(str(init)) elif len(self.choice) > 0: self.setLabel(self.choice[0]) else: self.setLabel('') if colour: self.backColour = colour else: self.backColour = POPUP_BACK_COLOUR if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def cleanup(self): self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.Unbind(wx.EVT_LEFT_DOWN, handler=self.MouseDown) self.outFunction = None def setBackgroundColour(self, col): self._backgroundColour = col self.SetBackgroundColour(col) wx.CallAfter(self.Refresh) def setBackColour(self, colour): self.backColour = colour wx.CallAfter(self.Refresh) def setEnable(self, enable): self._enable = enable wx.CallAfter(self.Refresh) def setChoice(self, choice, out=True): self.choice = choice self.setLabel(self.choice[0], out) wx.CallAfter(self.Refresh) def getChoice(self): return self.choice def OnPaint(self, event): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(self._backgroundColour, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(self._backgroundColour, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) rec = wx.Rect(0, 0, w, h) gc.SetBrush(wx.Brush(self.backColour)) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 2, rec[3] - 2, 3) font = wx.Font(MENU_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) dc.SetFont(font) if self._enable: gc.SetBrush(wx.Brush(POPUP_LABEL_COLOUR, wx.SOLID)) gc.SetPen(wx.Pen(POPUP_LABEL_COLOUR, width=1, style=wx.SOLID)) dc.SetTextForeground(POPUP_LABEL_COLOUR) else: gc.SetBrush(wx.Brush(POPUP_DISABLE_LABEL_COLOUR, wx.SOLID)) gc.SetPen(wx.Pen(POPUP_DISABLE_LABEL_COLOUR, width=1, style=wx.SOLID)) dc.SetTextForeground(POPUP_DISABLE_LABEL_COLOUR) dc.DrawLabel(self.label, wx.Rect(5, 0, w, h - 1), wx.ALIGN_CENTER_VERTICAL) tri = [(w - 13, h / 2 - 1), (w - 7, 5), (w - 7, h - 7), (w - 13, h / 2 - 1)] gc.DrawLines(tri) def MouseDown(self, event): if self._enable: self.PopupMenu(MenuFrame(self, self.choice), event.GetPosition()) def setLabel(self, label, out=False): self.label = label self.Refresh() if self.outFunction and self.label != '' and out: self.outFunction(self.choice.index(self.label), self.label) def setByIndex(self, ind, out=False): self.setLabel(self.choice[ind], out) def getLabel(self): return self.label def getIndex(self): return self.choice.index(self.label) def setStringSelection(self, selection): if selection in self.choice: self.setLabel(selection) class MySoundfileDropTarget(wx.FileDropTarget): def __init__(self, window): wx.FileDropTarget.__init__(self) self.window = window def OnDropFiles(self, x, y, filenames): if os.path.isfile(filenames[0]): self.window.parent.updateMenuFromPath(filenames[0]) elif os.path.isdir(filenames[0]): self.window.parent.updateMenuFromPath(filenames[0]) else: pass class FolderPopup(wx.Panel): def __init__(self, parent, path=None, init='', outFunction=None, emptyFunction=None, backColour=None, tooltip=''): wx.Panel.__init__(self, parent, -1, size=(130, 20)) self.parent = parent drop = MySoundfileDropTarget(self) self.SetDropTarget(drop) self.SetMaxSize((130, 20)) self.SetBackgroundColour(BACKGROUND_COLOUR) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_RIGHT_DOWN, self.MouseRightDown) self.backColour = backColour self._enable = True self.outFunction = outFunction self.emptyFunction = emptyFunction self.tooltip = tooltip self.tip = wx.ToolTip(self.tooltip) if CeciliaLib.getVar("useTooltips"): self.SetToolTip(self.tip) self.choice = [] self.arrowRect = wx.Rect(110, 0, 20, 20) if init in self.choice: self.setLabel(init) elif len(self.choice) > 0: self.setLabel(self.choice[0]) else: self.setLabel('') if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def setEnable(self, enable): self._enable = enable wx.CallAfter(self.Refresh) def reset(self): self.choice = [] self.setLabel('') wx.CallAfter(self.Refresh) def setChoice(self, choice): self.choice = choice def setLabel(self, label, out=True): self.label = label self.Refresh() if self.outFunction and self.label != '' and out: self.outFunction(self.choice.index(self.label), self.label) if CeciliaLib.getVar("useTooltips") and self.label != '': self.tip.SetTip(self.tooltip + '\n\nCurrent choice:\n' + self.label) elif CeciliaLib.getVar("useTooltips"): self.tip.SetTip(self.tooltip) else: self.tip.SetTip(self.label) def setByIndex(self, ind): self.label = self.choice[ind] wx.CallAfter(self.Refresh) def setBackColour(self, colour): self.backColour = colour wx.CallAfter(self.Refresh) def MouseDown(self, event): if self._enable: if self.arrowRect.Contains(event.GetPosition()) and self.choice != []: self.PopupMenu(MenuFrame(self, self.choice), event.GetPosition()) else: if self.emptyFunction: self.emptyFunction() def MouseRightDown(self, event): if self._enable: lastfiles = CeciliaLib.getVar("lastAudioFiles") if lastfiles != "": lastfiles = lastfiles.split(";") self.PopupMenu(MenuFrame(self, lastfiles), event.GetPosition()) else: if self.emptyFunction: self.emptyFunction() def OnPaint(self, event): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) if self._enable: if self.backColour: backColour = self.backColour else: backColour = POPUP_BACK_COLOUR else: backColour = POPUP_DISABLE_COLOUR rec = wx.Rect(0, 0, w, h) gc.SetBrush(wx.Brush(backColour)) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 1, rec[3] - 1, 3) font = wx.Font(MENU_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) dc.SetFont(font) gc.SetBrush(wx.Brush(POPUP_LABEL_COLOUR, wx.SOLID)) gc.SetPen(wx.Pen(POPUP_LABEL_COLOUR, width=1, style=wx.SOLID)) dc.SetTextForeground(POPUP_LABEL_COLOUR) dc.DrawLabel(CeciliaLib.shortenName(self.label, 19), wx.Rect(5, 0, w, h), wx.ALIGN_CENTER_VERTICAL) tri = [(w - 13, h / 2 - 1), (w - 7, 5), (w - 7, h - 7), (w - 13, h / 2 - 1)] gc.DrawLines(tri) #--------------------------- # Label (immutable) # -------------------------- class MainLabel(wx.Panel): def __init__(self, parent, label, size=(100, 20), font=None, colour=None, outFunction=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundColour(BACKGROUND_COLOUR) self.label = label self.italic = False self.font = font if colour: self.colour = colour else: self.colour = LABEL_BACK_COLOUR self.borderPen = wx.Pen(WIDGET_BORDER_COLOUR, width=1) self.backgroundBrush = wx.Brush(BACKGROUND_COLOUR, wx.SOLID) self.foregroundBrush = wx.Brush(self.colour) self.outFunction = outFunction self.Bind(wx.EVT_PAINT, self.OnPaint) if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def cleanup(self): self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.outFunction = None def setBackColour(self, colour): self.colour = colour self.foregroundBrush = wx.Brush(self.colour) wx.CallAfter(self.Refresh) def setLabel(self, label): self.italic = False self.label = label wx.CallAfter(self.Refresh) def OnPaint(self, event): curSize = self.GetSize() if not curSize.IsFullySpecified(): print("Something wrong with Label size...") return w, h = curSize.x, curSize.y dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(self.backgroundBrush) dc.Clear() if self.font: dc.SetFont(self.font) else: if self.italic: font = wx.Font(LABEL_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT) dc.SetFont(font) else: font = wx.Font(LABEL_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) dc.SetFont(font) rec = wx.Rect(0, 0, w, h) gc.SetBrush(self.foregroundBrush) gc.SetPen(self.borderPen) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 1, rec[3] - 1, 3) dc.SetTextForeground(LABEL_LABEL_COLOUR) dc.DrawLabel(self.label, wx.Rect(0, 1, w - 5, h - 1), wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) def setItalicLabel(self, label): self.italic = True self.label = label wx.CallAfter(self.Refresh) def getLabel(self): return self.label class Label(MainLabel): def __init__(self, parent, label, size=(100, 20), font=None, colour=None, outFunction=None, dclickFunction=None): MainLabel.__init__(self, parent=parent, label=label, size=size, font=font, colour=colour, outFunction=outFunction) self.dclickFunction = dclickFunction self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) def cleanup(self): self.Unbind(wx.EVT_LEFT_DOWN, handler=self.OnLeftDown) self.Unbind(wx.EVT_RIGHT_DOWN, handler=self.OnRightDown) self.Unbind(wx.EVT_LEFT_DCLICK, handler=self.OnDoubleClick) self.dclickFunction = None MainLabel.cleanup(self) def OnLeftDown(self, event): xsize = self.GetSize()[0] xpos = event.GetPosition()[0] if xpos < (xsize // 2): side = 'left' else: side = 'right' if self.outFunction: if event.ShiftDown(): self.outFunction(self.label, True, side=side) else: self.outFunction(self.label, side=side) def OnRightDown(self, event): xsize = self.GetSize()[0] xpos = event.GetPosition()[0] if xpos < (xsize // 2): side = 'left' else: side = 'right' if self.outFunction and not CeciliaLib.getVar("audioServer").isAudioServerRunning(): if event.ShiftDown(): self.outFunction(self.label, True, True, side=side) else: self.outFunction(self.label, alt=True, side=side) def OnDoubleClick(self, evt): xsize = self.GetSize()[0] xpos = evt.GetPosition()[0] if xpos < (xsize // 2): side = 'left' else: side = 'right' if self.dclickFunction is not None: self.dclickFunction(side) class OutputLabel(MainLabel): def __init__(self, parent, label, size=(100, 20), font=None, colour=None, outFunction=None): MainLabel.__init__(self, parent=parent, label=label, size=size, font=font, colour=colour, outFunction=outFunction) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) def OnLeftDown(self, event): if self.outFunction: self.outFunction() class PeakLabel(MainLabel): def __init__(self, parent, label, size=(100, 20), font=None, colour=None, gainSlider=None): MainLabel.__init__(self, parent=parent, label=label, size=size, font=font, colour=colour) self.gainSlider = gainSlider self.canCmdClick = True self.Bind(wx.EVT_LEFT_DCLICK, self.onDoubleClick) self.Bind(wx.EVT_LEFT_DOWN, self.onClick) def onDoubleClick(self, evt): self.setLabel('-90.00 dB') CeciliaLib.getControlPanel().resetVuMeter() def onClick(self, evt): if evt.CmdDown() and self.canCmdClick: try: mod = eval(self.getLabel().strip('+ dB')) * -1 self.gainSlider.SetValue(self.gainSlider.GetValue() + mod) self.canCmdClick = False except: return def setLabel(self, label): self.italic = False self.label = label self.canCmdClick = True wx.CallAfter(self.Refresh) class FrameLabel(wx.Panel): def __init__(self, parent, label, size=(100, 20), font=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundColour(BACKGROUND_COLOUR) self.label = label self.font = font if colour: self.colour = colour else: self.colour = TITLE_BACK_COLOUR self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.Bind(wx.EVT_MOTION, self.OnMotion) def setBackColour(self, colour): self.colour = colour wx.CallAfter(self.Refresh) def setLabel(self, label): self.label = label wx.CallAfter(self.Refresh) def MouseDown(self, event): self.pos = event.GetPosition() self.CaptureMouse() event.Skip() def MouseUp(self, event): if self.HasCapture(): self.ReleaseMouse() event.Skip() def OnMotion(self, event): if self.HasCapture(): screenPos = wx.GetMousePosition() newPos = [screenPos[0] - self.pos[0], screenPos[1] - self.pos[1]] if newPos[0] < 0: newPos[0] = 0 if newPos[1] < 0: newPos[1] = 0 self.GetParent().GetParent().SetPosition(newPos) def OnPaint(self, event): w, h = self.GetSize() dc = wx.PaintDC(self) dc.SetBrush(wx.Brush(TITLE_BACK_COLOUR, wx.SOLID)) dc.Clear() if self.font: dc.SetFont(self.font) else: font = wx.Font(LABEL_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) dc.SetFont(font) # Draw background dc.SetPen(wx.Pen(WHITE_COLOUR, width=1, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) rec = wx.Rect(0, 0, w - 5, h) dc.SetTextForeground(LABEL_LABEL_COLOUR) dc.DrawLabel(self.label, rec, wx.ALIGN_CENTER) class AboutLabel(wx.Panel): def __init__(self, parent, version, copyright, size=(600, 80), font=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundColour(BACKGROUND_COLOUR) self.version = version self.copyright = copyright self.font = font if colour: self.colour = colour else: self.colour = TITLE_BACK_COLOUR self.img_side = 70 self.bit = CeciliaLib.getVar("ICON_CECILIA_ABOUT_SMALL") self.Bind(wx.EVT_PAINT, self.OnPaint) if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def setBackColour(self, colour): self.colour = colour wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(TITLE_BACK_COLOUR, wx.SOLID)) dc.Clear() if self.font: dc.SetFont(self.font) else: font = wx.Font(LABEL_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) dc.SetFont(font) # Draw background dc.SetPen(wx.Pen(WHITE_COLOUR, width=1, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) dc.DrawBitmap(self.bit, w // 2 - self.img_side // 2, h // 2 - self.img_side // 2) gc.SetBrush(wx.Brush(TITLE_BACK_COLOUR, wx.TRANSPARENT)) gc.SetPen(wx.Pen(TITLE_BACK_COLOUR, width=3, style=wx.SOLID)) gc.DrawRoundedRectangle(w / 2 - self.img_side / 2 + 1, h / 2 - self.img_side / 2 + 1, self.img_side - 2, self.img_side - 2, self.img_side / 2 - 1) dc.SetTextForeground(LABEL_LABEL_COLOUR) rec = wx.Rect(10, 68, 50, 10) dc.DrawLabel(self.copyright, rec, wx.ALIGN_CENTER) rec = wx.Rect(540, 68, 50, 10) dc.DrawLabel(self.version, rec, wx.ALIGN_CENTER) #--------------------------- # Toggle (return 0 or 1) # -------------------------- class Toggle(wx.Panel): def __init__(self, parent, state, size=(20, 20), outFunction=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.outFunction = outFunction self.state = state if colour: self.colour = colour else: self.colour = POPUP_BACK_COLOUR self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def OnPaint(self, event): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) gc.SetBrush(wx.Brush(self.colour, wx.SOLID)) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1, style=wx.SOLID)) rec = wx.Rect(0, 0, w, h) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 1, rec[3] - 1, 3) dc.SetTextForeground(TOGGLE_LABEL_COLOUR) if self.state: label = 'X' else: label = '' dc.DrawLabel(label, wx.Rect(0, 0, w, h), wx.ALIGN_CENTER) if self.outFunction: self.outFunction(self.state) def MouseDown(self, event): if self.state: self.state = 0 else: self.state = 1 wx.CallAfter(self.Refresh) event.Skip() def OnEnter(self, event): if event.LeftIsDown(): if self.state: self.state = 0 else: self.state = 1 wx.CallAfter(self.Refresh) event.Skip() def getValue(self): return self.state def setValue(self, value): self.state = value wx.CallAfter(self.Refresh) #--------------------------- # Xfade switcher (return 0, 1 or 2) # -------------------------- class XfadeSwitcher(wx.Panel): def __init__(self, parent, state, size=(20, 20), outFunction=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.outFunction = outFunction self.state = state if colour: self.colour = colour else: self.colour = POPUP_BACK_COLOUR self.bitmaps = [CeciliaLib.getVar("ICON_XFADE_LINEAR"), CeciliaLib.getVar("ICON_XFADE_POWER"), CeciliaLib.getVar("ICON_XFADE_SIGMOID")] self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) CeciliaLib.setToolTip(self, TT_SAMPLER_XFADE_SHAPE) if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def OnPaint(self, event): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) gc.SetBrush(wx.Brush(self.colour, wx.SOLID)) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1, style=wx.SOLID)) rec = wx.Rect(0, 0, w, h) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 1, rec[3] - 1, 3) dc.SetPen(wx.Pen(TOGGLE_LABEL_COLOUR, width=1, style=wx.SOLID)) dc.DrawBitmap(self.bitmaps[self.state], 3, 3, True) if self.outFunction: self.outFunction(self.state) def MouseDown(self, event): self.state = (self.state + 1) % 3 wx.CallAfter(self.Refresh) event.Skip() def getValue(self): return self.state def setValue(self, value): self.state = value wx.CallAfter(self.Refresh) #--------------------------- # Button (send a trigger) # -------------------------- class Button(wx.Panel): def __init__(self, parent, size=(20, 20), outFunction=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.state = False self.outFunction = outFunction if colour: self.colour = colour[0] self.pushColour = '#222222' #colour[1] else: self.colour = POPUP_BACK_COLOUR self.pushColour = '#222222' self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def OnPaint(self, event): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) if not self.state: gc.SetBrush(wx.Brush(self.colour, wx.SOLID)) else: gc.SetBrush(wx.Brush(self.pushColour, wx.SOLID)) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1, style=wx.SOLID)) rec = wx.Rect(0, 0, w, h) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 1, rec[3] - 1, 9) def MouseDown(self, event): self.state = True if self.outFunction: self.outFunction(1) wx.CallAfter(self.Refresh) event.Skip() def MouseUp(self, event): self.state = False if self.outFunction: self.outFunction(0) wx.CallAfter(self.Refresh) event.Skip() #--------------------------- # Clocker (immutable) # -------------------------- class Clocker(wx.Panel): def __init__(self, parent, size=(80, 24), backgroundColour=None, borderColour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) if backgroundColour: self.backgroundColour = backgroundColour else: self.backgroundColour = TITLE_BACK_COLOUR self.SetBackgroundColour(self.backgroundColour) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) if borderColour: self.borderColour = borderColour else: self.borderColour = WIDGET_BORDER_COLOUR self.time = '00:00:00' self.font = wx.Font(CLOCKER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) self.Bind(wx.EVT_PAINT, self.OnPaint) self.createBackgroundBitmap() CeciliaLib.setToolTip(self, TT_CLOCK) def createBackgroundBitmap(self): w, h = self.GetSize() self.backgroundBitmap = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(self.backgroundBitmap) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(self.backgroundColour, wx.SOLID)) # Draw background dc.SetPen(wx.Pen(self.backgroundColour, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) rec = wx.Rect(0, 0, w, h) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) gc.SetBrush(wx.Brush(TR_CLOCKER_BACK_COLOUR)) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 2, rec[3] - 2, 4) dc.SelectObject(wx.NullBitmap) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.Clear() dc.DrawBitmap(self.backgroundBitmap, 0, 0) dc.SetFont(self.font) dc.SetTextForeground(LABEL_LABEL_COLOUR) dc.DrawLabel(self.time, wx.Rect(0, 0, w, h), wx.ALIGN_CENTER) def setTime(self, m, s, c): self.time = '%02d:%02d:%02d' % (m, s, c) wx.CallAfter(self.Refresh) #--------------------------- # EntryUnit # -------------------------- class EntryUnit(wx.Panel): def __init__(self, parent, value=0, unit='', size=(120, 20), valtype='float', outFunction=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size, style=wx.WANTS_CHARS) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.value = value self.unit = unit self.valtype = valtype self.outFunction = outFunction self.selected = False self.clickPos = None self.oldValue = value self.increment = 0.001 self.new = '' self.sizeX = size[0] self.font = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) if CeciliaLib.getVar("systemPlatform") == 'win32': self.unitFont = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) else: self.unitFont = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT) if self.sizeX == 120: self.entryRect = wx.Rect(15, 1, 77, self.GetSize()[1] - 2) else: self.entryRect = wx.Rect(15, 1, 57, self.GetSize()[1] - 2) if CeciliaLib.getVar("systemPlatform") == 'win32': if self.sizeX == 120: self.starttext = 80 else: self.starttext = 60 else: if self.sizeX == 120: self.starttext = 90 else: self.starttext = 70 if colour: self.backColour = colour else: self.backColour = ENTRYUNIT_BACK_COLOUR self.createBackgroundBitmap() self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_MOTION, self.MouseMotion) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.Bind(wx.EVT_CHAR, self.onChar) self.Bind(wx.EVT_KILL_FOCUS, self.LooseFocus) def cleanup(self): self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.Unbind(wx.EVT_LEFT_DOWN, handler=self.MouseDown) self.Unbind(wx.EVT_MOTION, handler=self.MouseMotion) self.Unbind(wx.EVT_LEFT_UP, handler=self.MouseUp) self.Unbind(wx.EVT_CHAR, handler=self.onChar) self.Unbind(wx.EVT_KILL_FOCUS, handler=self.LooseFocus) self.outFunction = None def createBackgroundBitmap(self): w, h = self.GetSize() self.backgroundBitmap = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(self.backgroundBitmap) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.SetTextForeground(LABEL_LABEL_COLOUR) # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) rec = wx.Rect(0, 0, w, h) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) gc.SetBrush(wx.Brush(self.backColour)) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 1, rec[3] - 1, 3) # Draw triangle gc.SetPen(wx.Pen(LABEL_LABEL_COLOUR, width=1, style=wx.SOLID)) gc.SetBrush(wx.Brush(LABEL_LABEL_COLOUR, wx.SOLID)) tri = [(12, h / 2 - 0.5), (7, 4.5), (7, h - 5.5), (12, h / 2 - 0.5)] gc.DrawLines(tri) # Draw unit dc.SetFont(self.unitFont) if self.sizeX == 120: dc.DrawLabel(self.unit, wx.Rect(95, 1, w - 95, h), wx.ALIGN_CENTER_VERTICAL) else: dc.DrawLabel(self.unit, wx.Rect(75, 1, w - 75, h), wx.ALIGN_CENTER_VERTICAL) dc.SelectObject(wx.NullBitmap) def setBackColour(self, colour): self.backColour = colour self.createBackgroundBitmap() wx.CallAfter(self.Refresh) def LooseFocus(self, event): if self.new != '': self.value = eval(self.new) self.new = '' self.selected = False if self.outFunction: self.outFunction(self.value) wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() dc.SetTextForeground(LABEL_LABEL_COLOUR) dc.DrawBitmap(self.backgroundBitmap, 0, 0) # Draw value dc.SetFont(self.font) if self.selected: dc.SetPen(wx.Pen(ENTRYUNIT_HIGHLIGHT_COLOUR, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(ENTRYUNIT_HIGHLIGHT_COLOUR, wx.SOLID)) dc.DrawRoundedRectangle(self.entryRect, 3) dc.SetPen(wx.Pen(LABEL_LABEL_COLOUR, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(LABEL_LABEL_COLOUR, wx.SOLID)) if self.selected and self.new: val = self.new else: val = str(self.value) width = len(val) * dc.GetCharWidth() dc.DrawLabel(val, wx.Rect(self.starttext - width, 1, width, h), wx.ALIGN_CENTER_VERTICAL) def MouseDown(self, event): pos = event.GetPosition() if self.entryRect.Contains(pos): self.clickPos = wx.GetMousePosition() self.oldValue = self.value offset = self.starttext - pos[0] if offset <= 7: self.increment = 0.001 elif offset <= 14: self.increment = 0.01 elif offset <= 21: self.increment = 0.1 elif offset <= 28: self.increment = 1 else: self.increment = 10 self.selected = True self.new = '' self.CaptureMouse() wx.CallAfter(self.Refresh) event.Skip() def MouseMotion(self, evt): if evt.Dragging() and evt.LeftIsDown() and self.HasCapture(): if self.clickPos is not None: pos = wx.GetMousePosition() off = self.clickPos[1] - pos[1] if self.valtype == 'float': off *= self.increment self.value = self.oldValue + off if self.outFunction: self.outFunction(self.value) wx.CallAfter(self.Refresh) def MouseUp(self, evt): if self.HasCapture(): self.ReleaseMouse() self.clickPos = None def onChar(self, event): if self.selected: char = '' if event.GetKeyCode() in range(wx.WXK_NUMPAD0, wx.WXK_NUMPAD9 + 1): char = str(event.GetKeyCode() - wx.WXK_NUMPAD0) elif event.GetKeyCode() in [wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT]: char = '-' elif event.GetKeyCode() in [wx.WXK_DECIMAL, wx.WXK_NUMPAD_DECIMAL]: char = '.' elif event.GetKeyCode() == wx.WXK_BACK: if self.new != '': self.new = self.new[0:-1] elif event.GetKeyCode() < 256: char = chr(event.GetKeyCode()) if char in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: self.new += char elif char == '.' and '.' not in self.new: self.new += char elif char == '-' and len(self.new) == 0: self.new += char elif event.GetKeyCode() in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: try: self.value = eval(self.new) except: pass self.new = '' self.selected = False if self.outFunction: self.outFunction(self.value) wx.CallAfter(self.Refresh) event.Skip() def setValue(self, val): self.value = val self.selected = False self.new = '' wx.CallAfter(self.Refresh) class RangeEntryUnit(wx.Panel): def __init__(self, parent, value=[0, 0], unit='', size=(120, 20), valtype='float', outFunction=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size, style=wx.WANTS_CHARS) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.value = value self.unit = unit self.valtype = valtype self.outFunction = outFunction self.selected = False self.clickPos = None self.oldValue = value self.increment = 0.001 self.new = '' self.font = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) if CeciliaLib.getVar("systemPlatform") == 'win32': self.unitFont = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) else: self.unitFont = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT) self.entryRect = wx.Rect(15, 1, 77, self.GetSize()[1] - 2) if CeciliaLib.getVar("systemPlatform") == 'win32': self.starttext = 80 elif CeciliaLib.getVar("systemPlatform").startswith("linux"): self.starttext = 80 else: self.starttext = 105 if colour: self.backColour = colour else: self.backColour = ENTRYUNIT_BACK_COLOUR self.createBackgroundBitmap() self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_MOTION, self.MouseMotion) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.Bind(wx.EVT_CHAR, self.onChar) self.Bind(wx.EVT_KILL_FOCUS, self.LooseFocus) def cleanup(self): self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.Unbind(wx.EVT_LEFT_DOWN, handler=self.MouseDown) self.Unbind(wx.EVT_MOTION, handler=self.MouseMotion) self.Unbind(wx.EVT_LEFT_UP, handler=self.MouseUp) self.Unbind(wx.EVT_CHAR, handler=self.onChar) self.Unbind(wx.EVT_KILL_FOCUS, handler=self.LooseFocus) self.outFunction = None def createBackgroundBitmap(self): w, h = self.GetSize() self.backgroundBitmap = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(self.backgroundBitmap) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.SetTextForeground(LABEL_LABEL_COLOUR) # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) rec = wx.Rect(0, 0, w, h) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) gc.SetBrush(wx.Brush(self.backColour)) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 1, rec[3] - 1, 3) # Draw triangle gc.SetPen(wx.Pen(LABEL_LABEL_COLOUR, width=1, style=wx.SOLID)) gc.SetBrush(wx.Brush(LABEL_LABEL_COLOUR, wx.SOLID)) tri = [(12, h / 2 - 0.5), (7, 4.5), (7, h - 5.5), (12, h / 2 - 0.5)] gc.DrawLines(tri) # Draw unit dc.SetFont(self.unitFont) dc.DrawLabel(self.unit, wx.Rect(95, 0, w - 95, h), wx.ALIGN_CENTER_VERTICAL) dc.SelectObject(wx.NullBitmap) def setBackColour(self, colour): self.backColour = colour self.createBackgroundBitmap() wx.CallAfter(self.Refresh) def LooseFocus(self, event): if self.new != '': self.value = eval(self.new) self.new = '' self.selected = False if self.outFunction: self.outFunction(self.value) wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() dc.SetTextForeground(LABEL_LABEL_COLOUR) dc.DrawBitmap(self.backgroundBitmap, 0, 0) # Draw value dc.SetFont(self.font) if self.selected: dc.SetPen(wx.Pen(ENTRYUNIT_HIGHLIGHT_COLOUR, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(ENTRYUNIT_HIGHLIGHT_COLOUR, wx.SOLID)) dc.DrawRoundedRectangle(self.entryRect, 3) dc.SetPen(wx.Pen(LABEL_LABEL_COLOUR, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(LABEL_LABEL_COLOUR, wx.SOLID)) if self.selected and self.new: val = self.new else: val = "%s, %s" % (self.value[0], self.value[1]) if CeciliaLib.getVar("systemPlatform").startswith("linux"): width = len(val) * (dc.GetCharWidth() - 2) else: width = len(val) * dc.GetCharWidth() dc.DrawLabel(val, wx.Rect(self.starttext - width, 0, width, h), wx.ALIGN_CENTER_VERTICAL) def MouseDown(self, event): pos = event.GetPosition() if self.entryRect.Contains(pos): if 0: # deactivate mouse scrolling for now self.clickPos = wx.GetMousePosition() self.oldValue = self.value offset = self.starttext - pos[0] if offset <= 7: self.increment = 0.001 elif offset <= 14: self.increment = 0.01 elif offset <= 21: self.increment = 0.1 elif offset <= 28: self.increment = 1 else: self.increment = 10 self.CaptureMouse() self.selected = True self.new = '' wx.CallAfter(self.Refresh) event.Skip() def MouseMotion(self, evt): if evt.Dragging() and evt.LeftIsDown() and self.HasCapture(): if self.clickPos is not None: pos = wx.GetMousePosition() off = self.clickPos[1] - pos[1] if self.valtype == 'float': off *= self.increment self.value = self.oldValue + off if self.outFunction: self.outFunction(self.value) wx.CallAfter(self.Refresh) def MouseUp(self, evt): if self.HasCapture(): self.ReleaseMouse() self.clickPos = None def onChar(self, event): if self.selected: char = '' if event.GetKeyCode() in range(wx.WXK_NUMPAD0, wx.WXK_NUMPAD9 + 1): char = str(event.GetKeyCode() - wx.WXK_NUMPAD0) elif event.GetKeyCode() in [wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT]: char = '-' elif event.GetKeyCode() in [wx.WXK_DECIMAL, wx.WXK_NUMPAD_DECIMAL]: char = '.' elif event.GetKeyCode() == 44: # comma char = ', ' elif event.GetKeyCode() == wx.WXK_BACK: if self.new != '': self.new = self.new[0:-1] elif event.GetKeyCode() < 256: char = chr(event.GetKeyCode()) if char in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: self.new += char elif char == '.' and self.new.count('.') <= 1: self.new += char elif char == ', ' and ', ' not in self.new: self.new += char elif char == '-' and len(self.new) == 0: self.new += char elif event.GetKeyCode() in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: tmp = self.new.split(', ') try: tmp = [eval(n.strip()) for n in tmp] if len(tmp) == 2: self.value = [min(tmp), max(tmp)] except: pass self.new = '' self.selected = False if self.outFunction: self.outFunction(self.value) wx.CallAfter(self.Refresh) event.Skip() def setValue(self, val): self.value = val self.selected = False self.new = '' wx.CallAfter(self.Refresh) class SplitterEntryUnit(wx.Panel): def __init__(self, parent, value=[0, 0, 0], unit='', size=(120, 20), num=3, valtype='float', outFunction=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size, style=wx.WANTS_CHARS) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.value = value self.unit = unit self.num = num self.valtype = valtype self.outFunction = outFunction self.selected = False self.clickPos = None self.oldValue = value self.increment = 0.001 self.new = '' self.font = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) if CeciliaLib.getVar("systemPlatform") == 'win32': self.unitFont = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) else: self.unitFont = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_LIGHT) self.entryRect = wx.Rect(5, 1, 87, self.GetSize()[1] - 2) if CeciliaLib.getVar("systemPlatform") == 'win32': self.starttext = 75 elif CeciliaLib.getVar("systemPlatform").startswith("linux"): self.starttext = 70 else: self.starttext = 105 if colour: self.backColour = colour else: self.backColour = ENTRYUNIT_BACK_COLOUR self.createBackgroundBitmap() self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_MOTION, self.MouseMotion) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.Bind(wx.EVT_CHAR, self.onChar) self.Bind(wx.EVT_KILL_FOCUS, self.LooseFocus) def cleanup(self): self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.Unbind(wx.EVT_LEFT_DOWN, handler=self.MouseDown) self.Unbind(wx.EVT_MOTION, handler=self.MouseMotion) self.Unbind(wx.EVT_LEFT_UP, handler=self.MouseUp) self.Unbind(wx.EVT_CHAR, handler=self.onChar) self.Unbind(wx.EVT_KILL_FOCUS, handler=self.LooseFocus) self.outFunction = None def createBackgroundBitmap(self): w, h = self.GetSize() self.backgroundBitmap = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(self.backgroundBitmap) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.SetTextForeground(LABEL_LABEL_COLOUR) # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) rec = wx.Rect(0, 0, w, h) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) gc.SetBrush(wx.Brush(self.backColour)) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 1, rec[3] - 1, 3) # Draw unit dc.SetFont(self.unitFont) dc.DrawLabel(self.unit, wx.Rect(95, 0, w - 95, h), wx.ALIGN_CENTER_VERTICAL) dc.SelectObject(wx.NullBitmap) def setBackColour(self, colour): self.backColour = colour self.createBackgroundBitmap() wx.CallAfter(self.Refresh) def LooseFocus(self, event): if self.new != '': self.value = eval(self.new) self.new = '' self.selected = False if self.outFunction: self.outFunction(self.value) wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() dc.SetTextForeground(LABEL_LABEL_COLOUR) dc.DrawBitmap(self.backgroundBitmap, 0, 0) # Draw value dc.SetFont(self.font) if self.selected: dc.SetPen(wx.Pen(ENTRYUNIT_HIGHLIGHT_COLOUR, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(ENTRYUNIT_HIGHLIGHT_COLOUR, wx.SOLID)) dc.DrawRoundedRectangle(self.entryRect, 3) dc.SetPen(wx.Pen(LABEL_LABEL_COLOUR, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(LABEL_LABEL_COLOUR, wx.SOLID)) if self.selected and self.new: val = self.new else: if self.valtype == "float": val = ["%i" % x for x in self.value] val = ", ".join(val) else: val = ["%i" % x for x in self.value] val = ", ".join(val) if CeciliaLib.getVar("systemPlatform").startswith("linux"): width = len(val) * (dc.GetCharWidth() - 2) else: width = len(val) * dc.GetCharWidth() dc.DrawLabel(val, wx.Rect(self.starttext - width, 0, width, h), wx.ALIGN_CENTER_VERTICAL) def MouseDown(self, event): pos = event.GetPosition() if self.entryRect.Contains(pos): if 0: # deactivate mouse scrolling for now self.clickPos = wx.GetMousePosition() self.oldValue = self.value offset = self.starttext - pos[0] if offset <= 7: self.increment = 0.001 elif offset <= 14: self.increment = 0.01 elif offset <= 21: self.increment = 0.1 elif offset <= 28: self.increment = 1 else: self.increment = 10 self.CaptureMouse() self.selected = True self.new = '' wx.CallAfter(self.Refresh) event.Skip() def MouseMotion(self, evt): if evt.Dragging() and evt.LeftIsDown() and self.HasCapture(): if self.clickPos is not None: pos = wx.GetMousePosition() off = self.clickPos[1] - pos[1] if self.valtype == 'float': off *= self.increment self.value = self.oldValue + off if self.outFunction: self.outFunction(self.value) wx.CallAfter(self.Refresh) def MouseUp(self, evt): if self.HasCapture(): self.ReleaseMouse() self.clickPos = None def onChar(self, event): if self.selected: char = '' if event.GetKeyCode() in range(wx.WXK_NUMPAD0, wx.WXK_NUMPAD9 + 1): char = str(event.GetKeyCode() - wx.WXK_NUMPAD0) elif event.GetKeyCode() in [wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT]: char = '-' elif event.GetKeyCode() in [wx.WXK_DECIMAL, wx.WXK_NUMPAD_DECIMAL]: char = '.' elif event.GetKeyCode() == 44: # comma. char = ', ' elif event.GetKeyCode() == wx.WXK_BACK: if self.new != '': self.new = self.new[0:-1] elif event.GetKeyCode() < 256: char = chr(event.GetKeyCode()) if char in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: self.new += char elif char == '.' and self.new.count('.') <= self.num: self.new += char elif char == ', ' and self.new.count(', ') <= (self.num - 2): self.new += char elif char == '-' and len(self.new) == 0: self.new += char elif event.GetKeyCode() in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: tmp = self.new.split(', ') try: tmp = sorted([eval(n.strip()) for n in tmp]) if len(tmp) == 3: self.value = tmp except: pass self.new = '' self.selected = False if self.outFunction: self.outFunction(self.value) wx.CallAfter(self.Refresh) event.Skip() def setValue(self, val): self.value = val self.selected = False self.new = '' wx.CallAfter(self.Refresh) #--------------------------- # ListEntry # -------------------------- class ListEntry(wx.Panel): def __init__(self, parent, value='1, .5, .25', size=(100, 20), colour=None, outFunction=None): wx.Panel.__init__(self, parent, -1, size=size, style=wx.WANTS_CHARS) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.outFunction = outFunction self.value = value self.new = '' self.font = wx.Font(ENTRYUNIT_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) if colour: self.backColour = colour else: self.backColour = ENTRYUNIT_BACK_COLOUR self.createBackgroundBitmap() self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) def createBackgroundBitmap(self): w, h = self.GetSize() self.backgroundBitmap = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(self.backgroundBitmap) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.SetTextForeground(LABEL_LABEL_COLOUR) # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) rec = wx.Rect(0, 0, w, h) gc.SetPen(wx.Pen(WIDGET_BORDER_COLOUR, width=1)) gc.SetBrush(wx.Brush(self.backColour)) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2] - 1, rec[3] - 1, 3) dc.SelectObject(wx.NullBitmap) def setBackColour(self, colour): self.backColour = colour self.createBackgroundBitmap() wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() dc.SetTextForeground(LABEL_LABEL_COLOUR) dc.DrawBitmap(self.backgroundBitmap, 0, 0) if type(self.value) == list: self.value = ", ".join([str(x) for x in self.value]) if ", " not in self.value: self.value = ", ".join(self.value.split()) val = CeciliaLib.shortenName(self.value, 19) # Draw value dc.SetFont(self.font) dc.SetPen(wx.Pen(LABEL_LABEL_COLOUR, width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(LABEL_LABEL_COLOUR, wx.SOLID)) dc.DrawLabel(val, wx.Rect(5, 0, w - 10, h), wx.ALIGN_CENTER_VERTICAL) def MouseDown(self, event): self.popup = ListEntryPopupFrame(self, self.value) self.popup.CenterOnScreen() self.popup.Show() def setValue(self, val): self.value = val if self.outFunction is not None: self.outFunction(self.value) wx.CallAfter(self.Refresh) def getValue(self): return self.value class ListEntryPopupFrame(wx.Frame): def __init__(self, parent, value): style = (wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_FLOAT_ON_PARENT) wx.Frame.__init__(self, parent, title='', style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.value = value self.SetClientSize((320, 95)) self.font = wx.Font(LIST_ENTRY_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) w, h = self.GetSize() panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) title = FrameLabel(panel, "ENTER LIST OF VALUES", size=(w - 2, 24)) box.Add(title, 0, wx.ALL, 1) self.entry = wx.TextCtrl(panel, -1, self.value, size=(300, 18), style=wx.TE_PROCESS_ENTER | wx.BORDER_NONE) self.entry.SetBackgroundColour(GRAPHER_BACK_COLOUR) self.entry.SetFont(self.font) self.entry.Bind(wx.EVT_TEXT_ENTER, self.OnApply) box.Add(self.entry, 0, wx.ALL, 10) applyBox = wx.BoxSizer(wx.HORIZONTAL) apply = ApplyToolBox(panel, tools=['Cancel', 'Apply'], outFunction=[self.OnCancel, self.OnApply]) applyBox.Add(apply, 0, wx.LEFT, 210) box.Add(applyBox) box.AddSpacer(10) panel.SetSizerAndFit(box) def OnApply(self, event=None): value = self.entry.GetValue().strip() if value[-1] == ",": value = value[:-1].strip() value = value.replace(" ", "").replace(",", ", ") self.parent.setValue(value) self.Destroy() def OnCancel(self, event=None): self.Destroy() class OSCPopupFrame(wx.Frame): def __init__(self, parent, slider, side='left'): style = (wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_FLOAT_ON_PARENT) wx.Frame.__init__(self, parent, title='', style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.slider = slider self.side = side self.value = init = outinit = "" self.SetClientSize((320, 140)) self.font = wx.Font(LIST_ENTRY_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) w, h = self.GetSize() panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) title = FrameLabel(panel, "Open Sound Control Input (port:address)", size=(w - 2, 24)) box.Add(title, 0, wx.ALL, 1) if self.slider.openSndCtrl is not None: osc = self.slider.openSndCtrl if self.slider.widget_type == "slider": init = "%d:%s" % (osc[0], osc[1]) elif self.slider.widget_type == "range": if side == 'left' and osc[0] != (): init = "%d:%s" % (osc[0][0], osc[0][1]) elif side == 'right' and osc[1] != (): init = "%d:%s" % (osc[1][0], osc[1][1]) if self.slider.OSCOut is not None: osc = self.slider.OSCOut if self.slider.widget_type == "slider": outinit = "%s:%d:%s" % (osc[0], osc[1], osc[2]) elif self.slider.widget_type == "range": if side == 'left' and osc[0] != (): outinit = "%s:%d:%s" % (osc[0][0], osc[0][1], osc[0][2]) elif side == 'right' and osc[1] != (): outinit = "%s:%d:%s" % (osc[1][0], osc[1][1], osc[1][2]) self.entry = wx.TextCtrl(panel, -1, init, size=(300, 18), style=wx.TE_PROCESS_ENTER | wx.BORDER_NONE) self.entry.SetFocus() self.entry.SetBackgroundColour(GRAPHER_BACK_COLOUR) self.entry.SetFont(self.font) self.entry.Bind(wx.EVT_TEXT_ENTER, self.OnApply) box.Add(self.entry, 0, wx.ALL, 10) outtext = wx.StaticText(panel, -1, label="OSC Output, optional (host:port:address)") outtext.SetForegroundColour("#FFFFFF") outtext.SetFont(self.font) box.Add(outtext, 0, wx.LEFT, 10) box.AddSpacer(2) self.entry2 = wx.TextCtrl(panel, -1, outinit, size=(300, 18), style=wx.TE_PROCESS_ENTER | wx.BORDER_NONE) self.entry2.SetBackgroundColour(GRAPHER_BACK_COLOUR) self.entry2.SetFont(self.font) box.Add(self.entry2, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) applyBox = wx.BoxSizer(wx.HORIZONTAL) apply = ApplyToolBox(panel, tools=['Cancel', 'Apply'], outFunction=[self.OnCancel, self.OnApply]) applyBox.Add(apply, 0, wx.LEFT, 210) box.Add(applyBox) box.AddSpacer(10) panel.SetSizerAndFit(box) def OnApply(self, event=None): self.value = self.entry.GetValue() outvalue = self.entry2.GetValue() if self.slider.widget_type == "slider": self.slider.setOSCInput(self.value) self.slider.setOSCOutput(outvalue) elif self.slider.widget_type == "range": self.slider.setOSCInput(self.value, self.side) self.slider.setOSCOutput(outvalue, self.side) self.Destroy() def OnCancel(self, event=None): self.Destroy() class BatchPopupFrame(wx.Frame): def __init__(self, parent, outFunction): style = (wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_FLOAT_ON_PARENT | wx.STAY_ON_TOP) wx.Frame.__init__(self, parent, title='', style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.outFunction = outFunction self.SetClientSize((320, 95)) self.font = wx.Font(LIST_ENTRY_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) w, h = self.GetSize() panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) title = FrameLabel(panel, "Enter the filename's suffix", size=(w - 2, 24)) box.Add(title, 0, wx.ALL, 1) self.entry = wx.TextCtrl(panel, -1, "", size=(300, 18), style=wx.TE_PROCESS_ENTER | wx.BORDER_NONE) self.entry.SetFocus() self.entry.SetBackgroundColour(GRAPHER_BACK_COLOUR) self.entry.SetFont(self.font) self.entry.Bind(wx.EVT_TEXT_ENTER, self.OnApply) box.Add(self.entry, 0, wx.ALL, 10) applyBox = wx.BoxSizer(wx.HORIZONTAL) apply = ApplyToolBox(panel, tools=['Cancel', 'Apply'], outFunction=[self.OnCancel, self.OnApply]) applyBox.Add(apply, 0, wx.LEFT, 210) box.Add(applyBox) box.AddSpacer(10) panel.SetSizerAndFit(box) def OnApply(self, event=None): wx.CallAfter(self.outFunction, self.entry.GetValue().strip()) self.Destroy() def OnCancel(self, event=None): wx.CallAfter(self.outFunction, "") self.Destroy() class AboutPopupFrame(wx.Frame): def __init__(self, parent, y_pos): style = (wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_FLOAT_ON_PARENT | wx.STAY_ON_TOP) wx.Frame.__init__(self, parent, title='', pos=(-1, y_pos), style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent if CeciliaLib.getVar("systemPlatform").startswith("linux") or CeciliaLib.getVar("systemPlatform") == 'win32': self.SetSize((600, 450)) self.font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) else: self.SetSize((600, 420)) self.font = wx.Font(13, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) w, h = self.GetSize() panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) title = AboutLabel(panel, APP_VERSION, APP_COPYRIGHT, size=(w - 2, 80)) box.Add(title, 0, wx.ALL, 1) self.rtc = rt.RichTextCtrl(panel, size=(w - 40, 280), style=wx.BORDER_NONE | wx.richtext.RE_READONLY) self.rtc.EnableVerticalScrollbar(False) self.rtc.SetBackgroundColour(BACKGROUND_COLOUR) self.rtc.SetFont(self.font) self.rtc.Freeze() self.rtc.BeginSuppressUndo() if CeciliaLib.getVar("systemPlatform").startswith("linux") or CeciliaLib.getVar("systemPlatform") == 'win32': self.rtc.BeginParagraphSpacing(0, 20) else: self.rtc.BeginParagraphSpacing(0, 40) self.rtc.BeginAlignment(wx.TEXT_ALIGNMENT_CENTER) self.rtc.Newline() self.rtc.BeginTextColour((255, 255, 255)) self.rtc.WriteText("Cecilia ") self.rtc.BeginTextColour((200, 200, 200)) self.rtc.WriteText("is a tool to make ear-bending noises and music. It uses the pyo audio engine created for the Python programming language by ") self.rtc.BeginTextColour((255, 255, 255)) self.rtc.WriteText(CeciliaLib.ensureNFD("Olivier Belanger ")) self.rtc.BeginTextColour((200, 200, 200)) self.rtc.WriteText(CeciliaLib.ensureNFD("at Universite de Montreal.")) self.rtc.Newline() self.rtc.BeginTextColour((255, 255, 255)) self.rtc.WriteText(CeciliaLib.ensureNFD("Jean Piche ")) self.rtc.BeginTextColour((200, 200, 200)) self.rtc.WriteText(CeciliaLib.ensureNFD("conceived, designed, and programmed Cecilia in 1995 to replace racks full of analog audio gear in a musique concrete studio.")) self.rtc.Newline() self.rtc.BeginTextColour((255, 255, 255)) self.rtc.WriteText(CeciliaLib.ensureNFD("Olivier Belanger ")) self.rtc.BeginTextColour((200, 200, 200)) self.rtc.WriteText("does all the programming and contributed heavily on design issues. He recoded Cecilia in Python from the ground up in 2008. Olivier is now the keeper of the program.") self.rtc.Newline() self.rtc.BeginTextColour((255, 255, 255)) self.rtc.WriteText("Jean-Michel Dumas ") self.rtc.BeginTextColour((200, 200, 200)) self.rtc.WriteText("translated almost every modules from Cecilia 4.2, created new ones and provided much needed moral support, patient testing and silly entertainment.") urlStyle = rt.RichTextAttr() urlStyle.SetTextColour(POPUP_BACK_COLOUR) urlStyle.SetFontUnderlined(True) self.rtc.Newline() self.rtc.BeginStyle(urlStyle) self.rtc.BeginURL("http://ajaxsoundstudio.com/software/cecilia/") self.rtc.WriteText("The Cecilia5 Web Site on AjaxSoundStudio.com") self.rtc.EndURL() self.rtc.EndStyle() self.rtc.Newline() self.rtc.EndParagraphSpacing() self.rtc.EndSuppressUndo() self.rtc.Thaw() self.rtc.Bind(wx.EVT_TEXT_URL, self.OnURL) box.Add(self.rtc, 0, wx.ALL, 10) closeBox = wx.BoxSizer(wx.HORIZONTAL) close = CloseBox(panel, outFunction=self.OnClose) closeBox.Add(close, 0, wx.LEFT, w // 2 - 25) box.Add(closeBox) panel.SetSizerAndFit(box) self.CenterOnScreen() self.Show() def OnURL(self, evt): webbrowser.open_new_tab("http://ajaxsoundstudio.com/software/cecilia/") def OnClose(self): self.Destroy() #--------------------------- # ControlKnob # -------------------------- class ControlKnob(wx.Panel): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(50, 70), log=False, outFunction=None, integer=False, backColour=None, label=''): wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size, style=wx.BORDER_NONE | wx.WANTS_CHARS) self.parent = parent self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.SetMinSize(self.GetSize()) self.knobBitmap = CeciliaLib.getVar("ICON_PLUGINS_KNOB") self.outFunction = outFunction self.integer = integer self.log = log self.label = label self.SetRange(minvalue, maxvalue) self.borderWidth = 1 self.selected = False self._enable = True self.new = '' self.floatPrecision = '%.3f' self.mode = 0 self.colours = {0: "#000000", 1: "#FF0000", 2: "#00FF00"} if backColour: self.backColour = backColour else: self.backColour = CONTROLSLIDER_BACK_COLOUR if init is not None: self.SetValue(init) self.init = init else: self.SetValue(minvalue) self.init = minvalue self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.Bind(wx.EVT_LEFT_DCLICK, self.DoubleClick) self.Bind(wx.EVT_MOTION, self.MouseMotion) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_CHAR, self.onChar) self.Bind(wx.EVT_KILL_FOCUS, self.LooseFocus) def setFloatPrecision(self, x): self.floatPrecision = '%.' + '%df' % x self.Refresh() def getMinValue(self): return self.minvalue def getMaxValue(self): return self.maxvalue def setEnable(self, enable): self._enable = enable if self._enable: self.knobBitmap = CeciliaLib.getVar("ICON_PLUGINS_KNOB") else: self.knobBitmap = CeciliaLib.getVar("ICON_PLUGINS_KNOB_DISABLE") self.Refresh() def getInit(self): return self.init def getLabel(self): return self.label def getLog(self): return self.log def SetRange(self, minvalue, maxvalue): self.minvalue = minvalue self.maxvalue = maxvalue def getRange(self): return [self.minvalue, self.maxvalue] def SetValue(self, value): value = CeciliaLib.clamp(value, self.minvalue, self.maxvalue) if self.log: t = CeciliaLib.toLog(value, self.minvalue, self.maxvalue) self.value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) else: t = CeciliaLib.tFromValue(value, self.minvalue, self.maxvalue) self.value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) if self.integer: self.value = int(self.value) self.selected = False self.Refresh() def GetValue(self): if self.log: t = CeciliaLib.tFromValue(self.value, self.minvalue, self.maxvalue) val = CeciliaLib.toExp(t, self.minvalue, self.maxvalue) else: val = self.value if self.integer: val = int(val) return val def LooseFocus(self, event): self.selected = False self.Refresh() def onChar(self, event): if self.selected: char = '' if event.GetKeyCode() in range(wx.WXK_NUMPAD0, wx.WXK_NUMPAD9 + 1): char = str(event.GetKeyCode() - wx.WXK_NUMPAD0) elif event.GetKeyCode() in [wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT]: char = '-' elif event.GetKeyCode() in [wx.WXK_DECIMAL, wx.WXK_NUMPAD_DECIMAL]: char = '.' elif event.GetKeyCode() == wx.WXK_BACK: if self.new != '': self.new = self.new[0:-1] elif event.GetKeyCode() < 256: char = chr(event.GetKeyCode()) if char in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: self.new += char elif char == '.' and '.' not in self.new: self.new += char elif char == '-' and len(self.new) == 0: self.new += char elif event.GetKeyCode() in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: if self.new != '': self.SetValue(eval(self.new)) self.new = '' self.selected = False self.Refresh() event.Skip() def MouseDown(self, evt): if evt.ShiftDown(): self.DoubleClick(evt) return if self._enable: rec = wx.Rect(5, 13, 45, 45) pos = evt.GetPosition() if rec.Contains(pos): self.clickPos = wx.GetMousePosition() self.oldValue = self.value self.CaptureMouse() self.selected = False self.Refresh() evt.Skip() def MouseUp(self, evt): if self.HasCapture(): self.ReleaseMouse() def DoubleClick(self, event): if self._enable: w, h = self.GetSize() pos = event.GetPosition() reclab = wx.Rect(3, 60, w - 3, 10) recpt = wx.Rect(self.knobPointPos[0] - 3, self.knobPointPos[1] - 3, 9, 9) if reclab.Contains(pos): self.selected = True elif recpt.Contains(pos): self.mode = (self.mode + 1) % 3 self.Refresh() event.Skip() def MouseMotion(self, evt): if self._enable: if evt.Dragging() and evt.LeftIsDown() and self.HasCapture(): pos = wx.GetMousePosition() offY = self.clickPos[1] - pos[1] off = offY off *= 0.005 * (self.maxvalue - self.minvalue) self.value = CeciliaLib.clamp(self.oldValue + off, self.minvalue, self.maxvalue) self.selected = False self.Refresh() def setbackColour(self, colour): self.backColour = colour def OnPaint(self, evt): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=self.borderWidth, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) dc.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) dc.SetTextForeground(CONTROLSLIDER_TEXT_COLOUR) # Draw text label reclab = wx.Rect(0, 1, w, 9) dc.DrawLabel(self.label, reclab, wx.ALIGN_CENTER_HORIZONTAL) recval = wx.Rect(0, 60, w, 10) if self.selected: dc.SetBrush(wx.Brush(CONTROLSLIDER_SELECTED_COLOUR, wx.SOLID)) dc.SetPen(wx.Pen(CONTROLSLIDER_SELECTED_COLOUR, width=self.borderWidth, style=wx.SOLID)) dc.DrawRoundedRectangle(recval, 3) dc.DrawBitmap(self.knobBitmap, 2, 13, True) r = 0.17320508075688773 # math.sqrt(.03) val = CeciliaLib.tFromValue(self.value, self.minvalue, self.maxvalue) * 0.87 ph = val * math.pi * 2 - (3 * math.pi / 2.2) X = int(round(r * math.cos(ph) * 45)) Y = int(round(r * math.sin(ph) * 45)) dc.SetPen(wx.Pen(self.colours[self.mode], width=1, style=wx.SOLID)) dc.SetBrush(wx.Brush(self.colours[self.mode], wx.SOLID)) self.knobPointPos = (X + 22, Y + 33) dc.DrawCircle(X + 22, Y + 33, 2) if not self.midiLearn: dc.SetFont(wx.Font(CONTROLSLIDER_FONT - 1, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) dc.DrawLabel(self.midictlLabel, wx.Rect(2, 12, 40, 40), wx.ALIGN_CENTER) else: dc.DrawLabel("?...", wx.Rect(2, 12, 40, 40), wx.ALIGN_CENTER) dc.SetFont(wx.Font(CONTROLSLIDER_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) # Draw text value if self.selected and self.new: val = self.new else: if self.integer: val = '%d' % self.GetValue() else: val = self.floatPrecision % self.GetValue() dc.SetTextForeground(CONTROLSLIDER_TEXT_COLOUR) dc.DrawLabel(val, recval, wx.ALIGN_CENTER) # Send value if self.outFunction: self.outFunction(self.GetValue()) evt.Skip() #--------------------------- # PlainSlider # -------------------------- class PlainSlider(wx.Panel): def __init__(self, parent, minvalue, maxvalue, init=None, pos=(0, 0), size=(80, 10), log=False, outFunction=None): wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size, style=wx.BORDER_NONE) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self._backColour = BACKGROUND_COLOUR self.SetMinSize(self.GetSize()) self.knobSize = 12 self.knobHalfSize = 6 self.sliderHeight = 6. self.outFunction = outFunction self.log = log self.SetRange(minvalue, maxvalue) self.borderWidth = 1 if init is not None: self.SetValue(init) else: self.SetValue(minvalue) self.clampPos() self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) self.Bind(wx.EVT_MOTION, self.MouseMotion) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnResize) self.show = True self.createSliderBitmap() self.createKnobBitmap() def createSliderBitmap(self): w, h = self.GetSize() b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) gc = wx.GraphicsContext_Create(dc) dc.SetPen(wx.Pen(self._backColour, width=1)) dc.SetBrush(wx.Brush(self._backColour)) dc.DrawRectangle(0, 0, w, h) gc.SetBrush(wx.Brush("#777777")) gc.SetPen(wx.Pen(self._backColour, width=0)) h2 = round(self.sliderHeight // 4) gc.DrawRoundedRectangle(0, h2, w - 1, self.sliderHeight - 1, 3) dc.SelectObject(wx.NullBitmap) b.SetMaskColour("#777777") self.sliderMask = b def createKnobBitmap(self): w, h = self.knobSize, self.GetSize()[1] b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) gc = wx.GraphicsContext_Create(dc) rec = wx.Rect(0, 0, w, h) dc.SetPen(wx.Pen(self._backColour, width=1)) dc.SetBrush(wx.Brush(self._backColour)) dc.DrawRectangle(rec) h2 = round(self.sliderHeight // 4) rec = wx.Rect(0, h2, w, self.sliderHeight) brush = gc.CreateLinearGradientBrush(0, h2, 0, h2 + self.sliderHeight, "#222240", CONTROLSLIDER_BACK_COLOUR) gc.SetBrush(brush) gc.DrawRoundedRectangle(0, 0, w, h, 2) dc.SelectObject(wx.NullBitmap) b.SetMaskColour("#787878") self.knobMask = b def setBackColour(self, col): self._backColour = col self.SetBackgroundColour(col) self.createSliderBitmap() self.createKnobBitmap() wx.CallAfter(self.Refresh) def Show(self): self.show = True wx.CallAfter(self.Refresh) def Hide(self): self.show = False wx.CallAfter(self.Refresh) def SetRange(self, minvalue, maxvalue): self.minvalue = minvalue self.maxvalue = maxvalue def scale(self): inter = CeciliaLib.tFromValue(self.pos, self.knobHalfSize, self.GetSize()[0] - self.knobHalfSize) return CeciliaLib.interpFloat(inter, self.minvalue, self.maxvalue) def SetValue(self, value): if self.HasCapture(): self.ReleaseMouse() value = CeciliaLib.clamp(value, self.minvalue, self.maxvalue) t = CeciliaLib.tFromValue(value, self.minvalue, self.maxvalue) if self.log: self.value = CeciliaLib.interpFloat(math.sqrt(t), self.minvalue, self.maxvalue) else: self.value = CeciliaLib.interpFloat(t, self.minvalue, self.maxvalue) self.clampPos() wx.CallAfter(self.Refresh) def GetValue(self): if self.log: t = CeciliaLib.tFromValue(self.value, self.minvalue, self.maxvalue) val = CeciliaLib.interpFloat(t * t, self.minvalue, self.maxvalue) else: val = self.value return val def MouseDown(self, evt): size = self.GetSize() self.pos = CeciliaLib.clamp(evt.GetPosition()[0], self.knobHalfSize, size[0] - self.knobHalfSize) self.value = self.scale() self.CaptureMouse() wx.CallAfter(self.Refresh) def MouseUp(self, evt): if self.HasCapture(): self.ReleaseMouse() def MouseMotion(self, evt): size = self.GetSize() if evt.Dragging() and evt.LeftIsDown(): self.pos = CeciliaLib.clamp(evt.GetPosition()[0], self.knobHalfSize, size[0] - self.knobHalfSize) self.value = self.scale() wx.CallAfter(self.Refresh) def OnResize(self, evt): self.createSliderBitmap() self.clampPos() wx.CallAfter(self.Refresh) def clampPos(self): size = self.GetSize() self.pos = CeciliaLib.tFromValue(self.value, self.minvalue, self.maxvalue) * (size[0] - self.knobHalfSize) + self.knobHalfSize self.pos = CeciliaLib.clamp(self.pos, self.knobHalfSize, size[0] - self.knobHalfSize) def OnPaint(self, evt): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(self._backColour, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(self._backColour, width=self.borderWidth, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) if self.show: # Draw inner part h2 = int(round(self.sliderHeight / 4)) rec = wx.Rect(0, h2, w, self.sliderHeight) dc.GradientFillLinear(rec, GRADIENT_DARK_COLOUR, CONTROLSLIDER_BACK_COLOUR, wx.BOTTOM) dc.DrawBitmap(self.sliderMask, 0, 0, True) # Draw knob rec = wx.Rect(self.pos - self.knobHalfSize, 0, self.knobSize, h) dc.GradientFillLinear(rec, GRADIENT_DARK_COLOUR, CONTROLSLIDER_KNOB_COLOUR, wx.RIGHT) dc.DrawBitmap(self.knobMask, rec[0], rec[1], True) # Send value if self.outFunction: self.outFunction(self.GetValue()) evt.Skip() #--------------------------- # ToolBox (need a list of outFunctions axxociated with tools) # -------------------------- class ToolBox(wx.Panel): def __init__(self, parent, size=(80, 20), tools=[], outFunction=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self._backColour = BACKGROUND_COLOUR self.parent = parent self.enabled = True if len(tools) == 0: raise('ToolBox must have at least a list of one tool!') self.num = len(tools) self.SetSize((20 * self.num, 20)) self.SetMinSize(self.GetSize()) self.SetMaxSize(self.GetSize()) self.tools = tools self.maps = {'save': self.onSave, 'load': self.onLoad, 'reset': self.onReset, 'show': self.onShow, 'open': self.onOpen, 'edit': self.onEdit, 'recycle': self.onRecycle, 'play': self.onPlay, 'time': self.onTime, 'delete': self.onDelete} self.graphics = {'save': [CeciliaLib.getVar("ICON_TB_SAVE"), CeciliaLib.getVar("ICON_TB_SAVE_OVER")], 'load': [CeciliaLib.getVar("ICON_TB_LOAD"), CeciliaLib.getVar("ICON_TB_LOAD_OVER")], 'reset': [CeciliaLib.getVar("ICON_TB_RESET"), CeciliaLib.getVar("ICON_TB_RESET_OVER")], 'delete': [CeciliaLib.getVar("ICON_TB_DELETE"), CeciliaLib.getVar("ICON_TB_DELETE_OVER")], 'play': [CeciliaLib.getVar("ICON_TB_PLAY"), CeciliaLib.getVar("ICON_TB_PLAY_OVER")], 'recycle': [CeciliaLib.getVar("ICON_TB_RECYCLE"), CeciliaLib.getVar("ICON_TB_RECYCLE_OVER")], 'edit': [CeciliaLib.getVar("ICON_TB_EDIT"), CeciliaLib.getVar("ICON_TB_EDIT_OVER")], 'open': [CeciliaLib.getVar("ICON_TB_OPEN"), CeciliaLib.getVar("ICON_TB_OPEN_OVER"), CeciliaLib.getVar("ICON_TB_CLOSE"), CeciliaLib.getVar("ICON_TB_CLOSE_OVER")], 'time': [CeciliaLib.getVar("ICON_TB_TIME"), CeciliaLib.getVar("ICON_TB_TIME_OVER")], 'show': [CeciliaLib.getVar("ICON_TB_SHOW"), CeciliaLib.getVar("ICON_TB_SHOW_OVER"), CeciliaLib.getVar("ICON_TB_HIDE"), CeciliaLib.getVar("ICON_TB_HIDE_OVER")]} self.rectList = [] for i in range(self.num): self.rectList.append(wx.Rect(i * 20, 0, 20, self.GetSize()[1])) self.overs = [False] * self.num self.oversWait = [True] * self.num self.show = True self.open = False self.samplerFrame = None self.outFunction = outFunction self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) def cleanup(self): self.Unbind(wx.EVT_PAINT, handler=self.OnPaint) self.Unbind(wx.EVT_LEFT_DOWN, handler=self.MouseDown) self.Unbind(wx.EVT_MOTION, handler=self.OnMotion) self.Unbind(wx.EVT_LEAVE_WINDOW, handler=self.OnLeave) self.outFunction = None self.maps = None def setBackColour(self, col): self._backColour = col self.SetBackgroundColour(col) wx.CallAfter(self.Refresh) def setOverWait(self, which): self.oversWait[which] = False def checkForOverReady(self, pos): for i, rec in enumerate(self.rectList): if not rec.Contains(pos): self.oversWait[i] = True def OnMotion(self, event): pos = event.GetPosition() self.overs = [False] * self.num for i, rec in enumerate(self.rectList): if rec.Contains(pos) and self.oversWait[i]: self.overs[i] = True self.checkForOverReady(pos) wx.CallAfter(self.Refresh) event.Skip() def OnLeave(self, event): self.overs = [False] * self.num self.oversWait = [True] * self.num wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(self._backColour, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(self._backColour, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) if self.enabled: for i, tool in enumerate(self.tools): if not self.overs[i]: if tool == 'show': if self.show: icon = self.graphics[tool][0] else: icon = self.graphics[tool][2] elif tool == 'open': if self.open: icon = self.graphics[tool][2] else: icon = self.graphics[tool][0] else: icon = self.graphics[tool][0] else: if tool == 'show': if self.show: icon = self.graphics[tool][1] else: icon = self.graphics[tool][3] elif tool == 'open': if self.open: icon = self.graphics[tool][3] else: icon = self.graphics[tool][1] else: icon = self.graphics[tool][1] dc.DrawBitmap(icon, self.rectList[i][0] + 2, self.rectList[i][1] + 1, True) dc.SetPen(wx.Pen(WHITE_COLOUR, width=1, style=wx.SOLID)) for i in range((self.num - 1)): dc.DrawLine((i + 1) * 20, 2, (i + 1) * 20, h - 2) def MouseDown(self, event): pos = event.GetPosition() for i, rec in enumerate(self.rectList): if rec.Contains(pos): tool = self.tools[i] self.setOverWait(i) break if self.outFunction: self.maps[tool]() wx.CallAfter(self.Refresh) def onSave(self): self.outFunction[self.tools.index('save')]() def onLoad(self): self.outFunction[self.tools.index('load')]() def onReset(self): self.outFunction[self.tools.index('reset')]() def onPlay(self): self.outFunction[self.tools.index('play')]() def onEdit(self): self.outFunction[self.tools.index('edit')]() def onRecycle(self): self.outFunction[self.tools.index('recycle')]() def onDelete(self): self.outFunction[self.tools.index('delete')]() def onOpen(self): self.outFunction[self.tools.index('open')]() if self.open: self.open = False else: self.open = True wx.CallAfter(self.Refresh) def onTime(self): self.outFunction[self.tools.index('time')]() def onShow(self): if self.show: self.show = False else: self.show = True self.outFunction[self.tools.index('show')](self.show) def setShow(self, state): self.show = state wx.CallAfter(self.Refresh) def setOpen(self, state): self.open = state wx.CallAfter(self.Refresh) def enable(self, state): self.enabled = state wx.CallAfter(self.Refresh) #--------------------------- # RadioToolBox (need a list of outFunctions axxociated with tools) # -------------------------- class RadioToolBox(wx.Panel): def __init__(self, parent, size=(75, 20), tools=['pointer', 'pencil', 'zoom', 'hand'], outFunction=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(TITLE_BACK_COLOUR) if len(tools) == 0: raise('ToolBox must have at least a list of one tool!') self.num = len(tools) self.SetSize((25 * self.num, 20)) self.SetMinSize(self.GetSize()) self.SetMaxSize(self.GetSize()) self.tools = tools self.maps = {'pointer': self.onPointer, 'pencil': self.onPencil, 'zoom': self.onZoom, 'hand': self.onHand} self.graphics = {'pointer': [CeciliaLib.getVar("ICON_RTB_POINTER"), CeciliaLib.getVar("ICON_RTB_POINTER_OVER"), CeciliaLib.getVar("ICON_RTB_POINTER_CLICK")], 'pencil': [CeciliaLib.getVar("ICON_RTB_PENCIL"), CeciliaLib.getVar("ICON_RTB_PENCIL_OVER"), CeciliaLib.getVar("ICON_RTB_PENCIL_CLICK")], 'zoom': [CeciliaLib.getVar("ICON_RTB_ZOOM"), CeciliaLib.getVar("ICON_RTB_ZOOM_OVER"), CeciliaLib.getVar("ICON_RTB_ZOOM_CLICK")], 'hand': [CeciliaLib.getVar("ICON_RTB_HAND"), CeciliaLib.getVar("ICON_RTB_HAND_OVER"), CeciliaLib.getVar("ICON_RTB_HAND_CLICK")]} self.rectList = [] for i in range(self.num): self.rectList.append(wx.Rect(i * 25, 0, 25, self.GetSize()[1])) self.overs = [False] * self.num self.oversWait = [True] * self.num self.selected = 0 self.outFunction = outFunction self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) def setOverWait(self, which): self.oversWait[which] = False def checkForOverReady(self, pos): for i, rec in enumerate(self.rectList): if not rec.Contains(pos): self.oversWait[i] = True def OnMotion(self, event): pos = event.GetPosition() self.overs = [False] * self.num for i, rec in enumerate(self.rectList): if rec.Contains(pos) and self.oversWait[i]: self.overs[i] = True self.checkForOverReady(pos) wx.CallAfter(self.Refresh) event.Skip() def OnLeave(self, event): self.overs = [False] * self.num self.oversWait = [True] * self.num wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(TITLE_BACK_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(TITLE_BACK_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) for i, tool in enumerate(self.tools): if not self.overs[i]: if i == self.selected: icon = self.graphics[tool][2] else: icon = self.graphics[tool][0] else: icon = self.graphics[tool][1] dc.DrawBitmap(icon, self.rectList[i][0] + 2, self.rectList[i][1] + 1, True) dc.SetPen(wx.Pen(WHITE_COLOUR, width=1, style=wx.SOLID)) for i in range((self.num - 1)): dc.DrawLine((i + 1) * 25, 2, (i + 1) * 25, h - 2) def setTool(self, tool): self.selected = self.tools.index(tool) if self.outFunction: self.maps[tool]() wx.CallAfter(self.Refresh) def MouseDown(self, event): pos = event.GetPosition() for i, rec in enumerate(self.rectList): if rec.Contains(pos): tool = self.tools[i] self.selected = i self.setOverWait(i) break if self.outFunction: self.maps[tool]() wx.CallAfter(self.Refresh) event.Skip() def onPointer(self): self.outFunction[self.tools.index('pointer')]() def onPencil(self): self.outFunction[self.tools.index('pencil')]() def onZoom(self): self.outFunction[self.tools.index('zoom')]() def onHand(self): self.outFunction[self.tools.index('hand')]() class PreferencesRadioToolBox(wx.Panel): def __init__(self, parent, size=(200, 30), tools=['path', 'audio', 'midi', 'filer', 'cecilia'], outFunction=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(TITLE_BACK_COLOUR) if len(tools) == 0: raise('ToolBox must have at least a list of one tool!') self.num = len(tools) self.SetSize((40 * self.num, 30)) self.SetMinSize(self.GetSize()) self.SetMaxSize(self.GetSize()) self.tools = tools self.graphics = {'path': [CeciliaLib.getVar("ICON_PREF_PATH"), CeciliaLib.getVar("ICON_PREF_PATH_OVER"), CeciliaLib.getVar("ICON_PREF_PATH_CLICK")], 'audio': [CeciliaLib.getVar("ICON_PREF_AUDIO"), CeciliaLib.getVar("ICON_PREF_AUDIO_OVER"), CeciliaLib.getVar("ICON_PREF_AUDIO_CLICK")], 'midi': [CeciliaLib.getVar("ICON_PREF_MIDI"), CeciliaLib.getVar("ICON_PREF_MIDI_OVER"), CeciliaLib.getVar("ICON_PREF_MIDI_CLICK")], 'filer': [CeciliaLib.getVar("ICON_PREF_FILER"), CeciliaLib.getVar("ICON_PREF_FILER_OVER"), CeciliaLib.getVar("ICON_PREF_FILER_CLICK")], 'cecilia': [CeciliaLib.getVar("ICON_PREF_CECILIA"), CeciliaLib.getVar("ICON_PREF_CECILIA_OVER"), CeciliaLib.getVar("ICON_PREF_CECILIA_CLICK")], } self.rectList = [] for i in range(self.num): self.rectList.append(wx.Rect(i * 40, 0, 40, self.GetSize()[1])) self.overs = [False] * self.num self.oversWait = [True] * self.num self.selected = 0 self.outFunction = outFunction self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) def setOverWait(self, which): self.oversWait[which] = False def checkForOverReady(self, pos): for i, rec in enumerate(self.rectList): if not rec.Contains(pos): self.oversWait[i] = True def OnMotion(self, event): pos = event.GetPosition() self.overs = [False] * self.num for i, rec in enumerate(self.rectList): if rec.Contains(pos) and self.oversWait[i]: self.overs[i] = True self.checkForOverReady(pos) wx.CallAfter(self.Refresh) event.Skip() def OnLeave(self, event): self.overs = [False] * self.num self.oversWait = [True] * self.num wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(TITLE_BACK_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(TITLE_BACK_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) for i, tool in enumerate(self.tools): if not self.overs[i]: if i == self.selected: icon = self.graphics[tool][2] else: icon = self.graphics[tool][0] else: icon = self.graphics[tool][1] dc.DrawBitmap(icon, self.rectList[i][0] + 2, self.rectList[i][1] + 1, True) dc.SetPen(wx.Pen(WHITE_COLOUR, width=1, style=wx.SOLID)) for i in range((self.num - 1)): dc.DrawLine((i + 1) * 40, 2, (i + 1) * 40, h - 2) def setTool(self, tool): self.selected = self.tools.index(tool) if self.outFunction: self.maps[tool]() wx.CallAfter(self.Refresh) def MouseDown(self, event): pos = event.GetPosition() for i, rec in enumerate(self.rectList): if rec.Contains(pos): self.selected = i self.setOverWait(i) break if self.outFunction: self.outFunction(self.selected) wx.CallAfter(self.Refresh) event.Skip() #--------------------------- # ApplyToolBox (need a list of outFunctions axxociated with tools) # -------------------------- class ApplyToolBox(wx.Panel): def __init__(self, parent, size=(100, 20), tools=['Close', 'Apply'], outFunction=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) if len(tools) == 0: raise('ToolBox must have at least a list of one tool!') self.num = len(tools) self.SetSize((50 * self.num, 20)) self.SetMinSize(self.GetSize()) self.SetMaxSize(self.GetSize()) self.tools = tools self.maps = {'Apply': self.onApply, 'Close': self.onClose, 'Cancel': self.onCancel} self.rectList = [] for i in range(self.num): self.rectList.append(wx.Rect(i * 50, 0, 50, self.GetSize()[1])) self.overs = [False] * self.num self.oversWait = [True] * self.num self.outFunction = outFunction self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def setOverWait(self, which): self.oversWait[which] = False def checkForOverReady(self, pos): for i, rec in enumerate(self.rectList): if not rec.Contains(pos): self.oversWait[i] = True def OnMotion(self, event): pos = event.GetPosition() self.overs = [False] * self.num for i, rec in enumerate(self.rectList): if rec.Contains(pos) and self.oversWait[i]: self.overs[i] = True self.checkForOverReady(pos) wx.CallAfter(self.Refresh) event.Skip() def OnLeave(self, event): self.overs = [False] * self.num self.oversWait = [True] * self.num wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) for i, tool in enumerate(self.tools): if not self.overs[i]: textColour = "#FFFFFF" else: textColour = "#000000" gc.SetBrush(wx.Brush(BUTTON_BACK_COLOUR)) gc.SetPen(wx.Pen("#FFFFFF", width=1)) rec = self.rectList[i] gc.DrawRoundedRectangle(rec[0] + 2, rec[1], rec[2] - 5, rec[3] - 1, 3) dc.SetFont(wx.Font(LABEL_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) dc.SetTextForeground(textColour) dc.DrawLabel(self.tools[i], self.rectList[i], wx.ALIGN_CENTER) def MouseDown(self, event): pos = event.GetPosition() for i, rec in enumerate(self.rectList): if rec.Contains(pos): tool = self.tools[i] self.setOverWait(i) break if self.outFunction: self.maps[tool]() wx.CallAfter(self.Refresh) event.Skip() def onApply(self): self.outFunction[self.tools.index('Apply')]() def onClose(self): self.outFunction[self.tools.index('Close')]() def onCancel(self): self.outFunction[self.tools.index('Cancel')]() class CloseBox(wx.Panel): def __init__(self, parent, size=(50, 20), pos=wx.DefaultPosition, outFunction=None, label='Close'): wx.Panel.__init__(self, parent, -1, size=size, pos=pos) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.setBackgroundColour(BACKGROUND_COLOUR) self.setInsideColour(BUTTON_BACK_COLOUR) self.SetSize(size) self.SetMinSize(self.GetSize()) self.SetMaxSize(self.GetSize()) self.label = label self.over = False self.overWait = True self.outFunction = outFunction self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def setBackgroundColour(self, colour): self._backColour = colour self.SetBackgroundColour(colour) wx.CallAfter(self.Refresh) def setInsideColour(self, colour): self._insideColour = colour wx.CallAfter(self.Refresh) def setOverWait(self): self.overWait = False def OnMotion(self, event): self.over = True wx.CallAfter(self.Refresh) event.Skip() def OnLeave(self, event): self.over = False self.overWait = True wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(self._backColour, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(self._backColour, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) if not self.over: textColour = "#FFFFFF" else: textColour = "#000000" gc.SetBrush(wx.Brush(self._insideColour)) gc.SetPen(wx.Pen("#FFFFFF", width=1)) gc.DrawRoundedRectangle(2, 0, w - 5, h - 1, 3) dc.SetFont(wx.Font(LABEL_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) dc.SetTextForeground(textColour) dc.DrawLabel(self.label, wx.Rect(2, 0, w - 4, h), wx.ALIGN_CENTER) def MouseDown(self, event): if self.outFunction: self.outFunction() wx.CallAfter(self.Refresh) event.Skip() #--------------------------- # PaletteToolBox (need a list of outFunctions axxociated with tools) # -------------------------- class PaletteToolBox(wx.Panel): def __init__(self, parent, size=(90, 20), tools=['random', 'waves', 'process'], outFunction=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(TITLE_BACK_COLOUR) self.randomFrame = RandomFrame(self) self.wavesFrame = WavesFrame(self) self.processFrame = ProcessFrame(self) if len(tools) == 0: raise('ToolBox must have at least a list of one tool!') self.num = len(tools) self.SetSize((30 * self.num, 20)) self.SetMinSize(self.GetSize()) self.SetMaxSize(self.GetSize()) self.tools = tools self.maps = {'random': self.onRandom, 'waves': self.onWaves, 'process': self.onProcess} self.graphics = {'random': [CeciliaLib.getVar("ICON_PTB_RANDOM"), CeciliaLib.getVar("ICON_PTB_RANDOM_OVER")], 'waves': [CeciliaLib.getVar("ICON_PTB_WAVES"), CeciliaLib.getVar("ICON_PTB_WAVES_OVER")], 'process': [CeciliaLib.getVar("ICON_PTB_PROCESS"), CeciliaLib.getVar("ICON_PTB_PROCESS_OVER")]} self.rectList = [] for i in range(self.num): self.rectList.append(wx.Rect(i * 30, 0, 30, self.GetSize()[1])) self.overs = [False] * self.num self.oversWait = [True] * self.num self.selected = 0 self.outFunction = outFunction self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) def setOverWait(self, which): self.oversWait[which] = False def checkForOverReady(self, pos): for i, rec in enumerate(self.rectList): if not rec.Contains(pos): self.oversWait[i] = True def OnMotion(self, event): pos = event.GetPosition() self.overs = [False] * self.num for i, rec in enumerate(self.rectList): if rec.Contains(pos) and self.oversWait[i]: self.overs[i] = True self.checkForOverReady(pos) wx.CallAfter(self.Refresh) event.Skip() def OnLeave(self, event): self.overs = [False] * self.num self.oversWait = [True] * self.num wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(TITLE_BACK_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(TITLE_BACK_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) for i, tool in enumerate(self.tools): if not self.overs[i]: icon = self.graphics[tool][0] else: icon = self.graphics[tool][1] dc.DrawBitmap(icon, self.rectList[i][0] + 2, self.rectList[i][1] + 1, True) dc.SetPen(wx.Pen(WHITE_COLOUR, width=1, style=wx.SOLID)) for i in range((self.num - 1)): dc.DrawLine((i + 1) * 30 + 1, 2, (i + 1) * 30 + 1, h - 2) def MouseDown(self, event): pos = event.GetPosition() for i, rec in enumerate(self.rectList): if rec.Contains(pos): tool = self.tools[i] self.selected = i self.setOverWait(i) break self.maps[tool]() wx.CallAfter(self.Refresh) def checkForOpenFrame(self, index): if index != 0: self.randomFrame.Hide() if index != 1: self.wavesFrame.Hide() if index != 2: self.processFrame.Hide() def onRandom(self): self.checkForOpenFrame(0) off = self.GetScreenPosition() pos = (off[0] - 200, off[1] + 20) self.randomFrame.SetPosition(pos) self.randomFrame.Show() def onWaves(self): self.checkForOpenFrame(1) off = self.GetScreenPosition() pos = (off[0] - 200, off[1] + 20) self.wavesFrame.SetPosition(pos) self.wavesFrame.Show() def onProcess(self): self.checkForOpenFrame(2) off = self.GetScreenPosition() pos = (off[0] - 200, off[1] + 20) self.processFrame.SetPosition(pos) self.processFrame.Show() def checkForSelection(self, selected): if len(selected) >= 2: grapher = CeciliaLib.getVar("grapher") line = grapher.plotter.getLine(grapher.plotter.getSelected()) minx = line.getData()[selected[0]][0] / CeciliaLib.getVar("totalTime") maxx = line.getData()[selected[-1]][0] / CeciliaLib.getVar("totalTime") addPointsBefore = [pt for i, pt in enumerate(line.normalize()) if i < selected[0]] addPointsAfter = [pt for i, pt in enumerate(line.normalize()) if i > selected[-1]] else: minx = 0 maxx = 1 addPointsBefore = [] addPointsAfter = [] return minx, maxx, addPointsBefore, addPointsAfter #--------------------------- # Grapher Palette frames # -------------------------- class RandomFrame(wx.Frame): def __init__(self, parent): style = (wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_FLOAT_ON_PARENT) wx.Frame.__init__(self, parent, title='', style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.SetClientSize((300, 240)) self.distList = ['Uniform', 'Gaussian', 'Weibull', 'Beta', 'Drunk', 'Loopseg', 'Repeater', 'DroneAndJump'] self.interpList = ['Linear', 'Sample hold'] panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) w, h = self.GetSize() panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) title = FrameLabel(panel, "STOCHASTIC GENERATOR", size=(w - 2, 24)) box.Add(title, 0, wx.ALL, 1) interpBox = wx.BoxSizer(wx.HORIZONTAL) interpLabel = wx.StaticText(panel, -1, "Interpolation") interpLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) interpLabel.SetForegroundColour(WHITE_COLOUR) interpBox.Add(interpLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 114) self.interpMenu = CustomMenu(panel, self.interpList, self.interpList[0]) CeciliaLib.setToolTip(self.interpMenu, TT_STOCH_INTERP) interpBox.Add(self.interpMenu, 0, wx.LEFT | wx.RIGHT, 5) slidersBox = wx.FlexGridSizer(5, 2, 5, 5) ptsLabel = wx.StaticText(panel, -1, "Points") ptsLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) ptsLabel.SetForegroundColour(WHITE_COLOUR) self.ptsSlider = ControlSlider(panel, 5, 1000, 50, size=(235, 15), integer=True, backColour=BACKGROUND_COLOUR) self.ptsSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.ptsSlider, TT_STOCH_POINTS) slidersBox.AddMany([(ptsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.ptsSlider, 0, wx.RIGHT, 5)]) minLabel = wx.StaticText(panel, -1, "Min") minLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) minLabel.SetForegroundColour(WHITE_COLOUR) self.minSlider = ControlSlider(panel, 0, 1, 0, size=(235, 15), backColour=BACKGROUND_COLOUR) self.minSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.minSlider, TT_STOCH_MIN) slidersBox.AddMany([(minLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.minSlider, 0, wx.RIGHT, 5)]) maxLabel = wx.StaticText(panel, -1, "Max") maxLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) maxLabel.SetForegroundColour(WHITE_COLOUR) self.maxSlider = ControlSlider(panel, 0, 1, 1, size=(235, 15), backColour=BACKGROUND_COLOUR) self.maxSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.maxSlider, TT_STOCH_MAX) slidersBox.AddMany([(maxLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.maxSlider, 0, wx.RIGHT, 5)]) x1Label = wx.StaticText(panel, -1, "x1") x1Label.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) x1Label.SetForegroundColour(WHITE_COLOUR) self.x1Slider = ControlSlider(panel, 0, 1, .5, size=(235, 15), backColour=BACKGROUND_COLOUR) self.x1Slider.setSliderHeight(10) CeciliaLib.setToolTip(self.x1Slider, TT_STOCH_X1) slidersBox.AddMany([(x1Label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.x1Slider, 0, wx.RIGHT, 5)]) x2Label = wx.StaticText(panel, -1, "x2") x2Label.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) x2Label.SetForegroundColour(WHITE_COLOUR) self.x2Slider = ControlSlider(panel, 0, 1, .5, size=(235, 15), backColour=BACKGROUND_COLOUR) self.x2Slider.setSliderHeight(10) CeciliaLib.setToolTip(self.x2Slider, TT_STOCH_X2) slidersBox.AddMany([(x2Label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.x2Slider, 0, wx.RIGHT, 5)]) slidersBox.AddGrowableCol(1) distBox = wx.BoxSizer(wx.HORIZONTAL) distLabel = wx.StaticText(panel, -1, "Distribution") distLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) distLabel.SetForegroundColour(WHITE_COLOUR) distBox.Add(distLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 120) self.distMenu = CustomMenu(panel, self.distList, self.distList[0], outFunction=self.onDistribution) CeciliaLib.setToolTip(self.distMenu, TT_STOCH_TYPE) self.distMenu.setLabel(self.distMenu.getLabel(), True) distBox.Add(self.distMenu, 0, wx.LEFT | wx.RIGHT, 5) applyBox = wx.BoxSizer(wx.HORIZONTAL) applyer = ApplyToolBox(panel, outFunction=[self.OnClose, self.OnApply]) applyBox.Add(applyer, 0, wx.RIGHT, 8) box.Add(distBox, 0, wx.ALL, 5) box.Add(interpBox, 0, wx.ALL, 5) box.Add(slidersBox, 0, wx.EXPAND | wx.ALL, 5) box.Add(applyBox, 0, wx.ALIGN_RIGHT | wx.TOP, 15) panel.SetSizerAndFit(box) panel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) title.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) def OnLooseFocus(self, event): if CeciliaLib.getVar("canGrabFocus"): win = wx.FindWindowAtPointer() if win[0] is not None: if win[0].GetTopLevelParent() == self: pass else: win = CeciliaLib.getVar("interface") win.Raise() def OnClose(self): self.Hide() def onDistribution(self, ind, label): if label == 'Uniform': self.x1Slider.Disable() self.x2Slider.Disable() else: self.x1Slider.Enable() self.x2Slider.Enable() def OnApply(self): dist = self.distMenu.getLabel() interp = self.interpMenu.getLabel() points = self.ptsSlider.GetValue() mini = self.minSlider.GetValue() maxi = self.maxSlider.GetValue() x1 = self.x1Slider.GetValue() x2 = self.x2Slider.GetValue() if dist == 'Uniform': dict = self.uniformGenerate(interp, points, mini, maxi) elif dist == 'Gaussian': dict = self.gaussGenerate(interp, points, mini, maxi, x1, x2) elif dist == 'Weibull': dict = self.weibullGenerate(interp, points, mini, maxi, x1, x2) elif dist == 'Beta': dict = self.betaGenerate(interp, points, mini, maxi, x1, x2) elif dist in ['Drunk', 'Loopseg']: dict = self.drunkGenerate(interp, points, mini, maxi, x1, x2, dist) elif dist in ['Repeater', 'DroneAndJump']: dict = self.repeatGenerate(interp, points, mini, maxi, x1, x2, dist) line = CeciliaLib.getVar("grapher").plotter.getLine(CeciliaLib.getVar("grapher").plotter.getSelected()) line.setLineState(dict) line.setShow(1) CeciliaLib.getVar("grapher").toolbar.toolbox.setShow(1) if line.getSlider() is not None: line.getSlider().setPlay(1) CeciliaLib.getVar("grapher").plotter.draw() def uniformGenerate(self, interp, points, mini, maxi): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / (points - 1) if addPointsBefore: templist.extend(addPointsBefore) templist.append([minx, addPointsBefore[-1][1]]) start_point = 1 else: start_point = 0 if interp == 'Linear': for i in range(start_point, points): x = i * step * (maxx - minx) + minx y = random.uniform(mini, maxi) templist.append([x, y]) elif interp == 'Sample hold': for i in range(points): x = i * step * (maxx - minx) + minx y = random.uniform(mini, maxi) templist.append([x, y]) if i != points - 1: xx = (i + 1) * step * (maxx - minx) + minx templist.append([xx, y]) if addPointsAfter: if interp == 'Linear': templist[-1] = [templist[-1][0], addPointsAfter[0][1]] elif interp == 'Sample hold': templist[-1] = [xx, addPointsAfter[0][1]] templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def gaussGenerate(self, interp, points, mini, maxi, x1, x2): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / (points - 1) x1 = x1 * .5 if addPointsBefore: templist.extend(addPointsBefore) templist.append([minx, addPointsBefore[-1][1]]) start_point = 1 else: start_point = 0 if interp == 'Linear': for i in range(start_point, points): x = i * step * (maxx - minx) + minx y = random.gauss(x2, x1) if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, y]) if interp == 'Sample hold': for i in range(points): x = i * step * (maxx - minx) + minx y = random.gauss(x2, x1) if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, y]) if i != points - 1: xx = (i + 1) * step * (maxx - minx) + minx templist.append([xx, y]) if addPointsAfter: if interp == 'Linear': templist[-1] = [templist[-1][0], addPointsAfter[0][1]] elif interp == 'Sample hold': templist[-1] = [xx, addPointsAfter[0][1]] templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def weibullGenerate(self, interp, points, mini, maxi, x1, x2): def check(x): if x <= 0.005: x = 0.005 return x selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / (points - 1) x1 = x1 * 10 if addPointsBefore: templist.extend(addPointsBefore) templist.append([minx, addPointsBefore[-1][1]]) start_point = 1 else: start_point = 0 if interp == 'Linear': for i in range(start_point, points): x = i * step * (maxx - minx) + minx y = random.weibullvariate(x2, check(x1)) if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, y]) if interp == 'Sample hold': for i in range(points): x = i * step * (maxx - minx) + minx y = random.weibullvariate(x2, check(x1)) if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, y]) if i != points - 1: xx = (i + 1) * step * (maxx - minx) + minx templist.append([xx, y]) if addPointsAfter: if interp == 'Linear': templist[-1] = [templist[-1][0], addPointsAfter[0][1]] elif interp == 'Sample hold': templist[-1] = [xx, addPointsAfter[0][1]] templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def betaGenerate(self, interp, points, mini, maxi, x1, x2): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / (points - 1) x1 = x1 * 10 + .001 x2 = x2 * 10 + .001 if addPointsBefore: templist.extend(addPointsBefore) templist.append([minx, addPointsBefore[-1][1]]) start_point = 1 else: start_point = 0 if interp == 'Linear': for i in range(start_point, points): x = i * step * (maxx - minx) + minx y = random.betavariate(x2, x1) if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, y]) if interp == 'Sample hold': for i in range(points): x = i * step * (maxx - minx) + minx y = random.betavariate(x2, x1) if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, y]) if i != points - 1: xx = (i + 1) * step * (maxx - minx) + minx templist.append([xx, y]) if addPointsAfter: if interp == 'Linear': templist[-1] = [templist[-1][0], addPointsAfter[0][1]] elif interp == 'Sample hold': templist[-1] = [xx, addPointsAfter[0][1]] templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def drunkGenerate(self, interp, points, mini, maxi, x1, x2, dist): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / (points - 1) minimum = int(mini * 1000) maximum = int(maxi * 1000) if maximum < minimum: tmp = minimum minimum = maximum maximum = tmp if dist == 'Drunk': drunk = Drunk(minimum, maximum) elif dist == 'Loopseg': drunk = Loopseg(minimum, maximum) drunk.setLastValue(int(x1 * (maximum - minimum) + minimum)) stepsize = -int((x2 * 0.5 * (maximum - minimum))) if addPointsBefore: templist.extend(addPointsBefore) if interp == 'Sample hold': templist.append([minx, addPointsBefore[-1][1]]) if interp == 'Linear': templist.append([minx, (x1 * (maximum - minimum) + minimum) * 0.001]) for i in range(1, points): x = i * step * (maxx - minx) + minx y = drunk.next(stepsize) * 0.001 if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, y]) if interp == 'Sample hold': templist.append([minx, (x1 * (maximum - minimum) + minimum) * 0.001]) templist.append([step * (maxx - minx) + minx, (x1 * (maximum - minimum) + minimum) * 0.001]) for i in range(1, points): x = i * step * (maxx - minx) + minx y = drunk.next(stepsize) * 0.001 if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, y]) if i != points - 1: xx = (i + 1) * step * (maxx - minx) + minx templist.append([xx, y]) if addPointsAfter: if interp == 'Linear': templist[-1] = [templist[-1][0], addPointsAfter[0][1]] elif interp == 'Sample hold': templist[-1] = [xx, addPointsAfter[0][1]] templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def repeatGenerate(self, interp, points, mini, maxi, x1, x2, dist): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / (points - 1) minimum = int(mini * 1000) maximum = int(maxi * 1000) if maximum < minimum: tmp = minimum minimum = maximum maximum = tmp if dist == 'Repeater': drunk = Repeater(minimum, maximum) elif dist == 'DroneAndJump': drunk = DroneAndJump(minimum, maximum) drunk.setLastValue(int(x1 * (maximum - minimum) + minimum)) stepsize = -int((x2 * 0.5 * (maximum - minimum))) last_y = (x1 * (maximum - minimum) + minimum) * 0.001 if addPointsBefore: templist.extend(addPointsBefore) if interp == 'Sample hold': templist.append([minx, addPointsBefore[-1][1]]) if interp == 'Linear': templist.append([minx, last_y]) for i in range(1, points): x = i * step * (maxx - minx) + minx y = drunk.next(stepsize) * 0.001 if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, y]) if interp == 'Sample hold': templist.append([minx, last_y]) for i in range(1, points): x = i * step * (maxx - minx) + minx y = drunk.next(stepsize) * 0.001 if y < mini: y = mini elif y > maxi: y = maxi templist.append([x, last_y]) templist.append([x, y]) last_y = y if templist[-1][0] != 1.0: templist.append([maxx, last_y]) if addPointsAfter: if interp == 'Linear': templist[-1] = [templist[-1][0], addPointsAfter[0][1]] elif interp == 'Sample hold': templist[-1] = [maxx, addPointsAfter[0][1]] templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} class WavesFrame(wx.Frame): def __init__(self, parent): style = (wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_FLOAT_ON_PARENT) wx.Frame.__init__(self, parent, title='', style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.SetClientSize((300, 210)) self.distList = ['Sine', 'Square', 'Triangle', 'Sawtooth', 'Sinc', 'Pulse', 'Bi-Pulse'] panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) w, h = self.GetSize() panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) title = FrameLabel(panel, "WAVEFORM GENERATOR", size=(w - 2, 24)) box.Add(title, 0, wx.ALL, 1) slidersBox = wx.FlexGridSizer(5, 2, 5, 5) ptsLabel = wx.StaticText(panel, -1, "Points") ptsLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) ptsLabel.SetForegroundColour(WHITE_COLOUR) self.ptsSlider = ControlSlider(panel, 5, 1000, 50, size=(235, 15), integer=True, backColour=BACKGROUND_COLOUR) self.ptsSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.ptsSlider, TT_WAVE_POINTS) slidersBox.AddMany([(ptsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.ptsSlider, 0, wx.RIGHT, 5)]) ampLabel = wx.StaticText(panel, -1, "Amp") ampLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) ampLabel.SetForegroundColour(WHITE_COLOUR) self.ampSlider = ControlSlider(panel, 0, 1, 1, size=(235, 15), backColour=BACKGROUND_COLOUR) self.ampSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.ampSlider, TT_WAVE_AMP) slidersBox.AddMany([(ampLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.ampSlider, 0, wx.RIGHT, 5)]) freqLabel = wx.StaticText(panel, -1, "Freq") freqLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) freqLabel.SetForegroundColour(WHITE_COLOUR) self.freqSlider = ControlSlider(panel, 0, 100, 1, size=(235, 15), backColour=BACKGROUND_COLOUR) self.freqSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.freqSlider, TT_WAVE_FREQ) slidersBox.AddMany([(freqLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.freqSlider, 0, wx.RIGHT, 5)]) phaseLabel = wx.StaticText(panel, -1, "Phase") phaseLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) phaseLabel.SetForegroundColour(WHITE_COLOUR) self.phaseSlider = ControlSlider(panel, 0, 1, 0, size=(235, 15), backColour=BACKGROUND_COLOUR) self.phaseSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.phaseSlider, TT_WAVE_PHASE) slidersBox.AddMany([(phaseLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.phaseSlider, 0, wx.RIGHT, 5)]) widthLabel = wx.StaticText(panel, -1, "Width") widthLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) widthLabel.SetForegroundColour(WHITE_COLOUR) self.widthSlider = ControlSlider(panel, 0, 1, .5, size=(235, 15), backColour=BACKGROUND_COLOUR) self.widthSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.widthSlider, TT_WAVE_WIDTH) slidersBox.AddMany([(widthLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.widthSlider, 0, wx.RIGHT, 5)]) slidersBox.AddGrowableCol(1) distBox = wx.BoxSizer(wx.HORIZONTAL) distLabel = wx.StaticText(panel, -1, "Shape") distLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) distLabel.SetForegroundColour(WHITE_COLOUR) distBox.Add(distLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 147) self.distMenu = CustomMenu(panel, self.distList, self.distList[0], outFunction=self.onDistribution) self.distMenu.setLabel(self.distMenu.getLabel(), True) CeciliaLib.setToolTip(self.distMenu, TT_WAVE_SHAPE) distBox.Add(self.distMenu, 0, wx.LEFT | wx.RIGHT, 5) applyBox = wx.BoxSizer(wx.HORIZONTAL) apply = ApplyToolBox(panel, outFunction=[self.OnClose, self.OnApply]) applyBox.Add(apply, 0, wx.RIGHT, 8) box.Add(distBox, 0, wx.ALL, 5) box.Add(slidersBox, 0, wx.EXPAND | wx.ALL, 5) box.Add(applyBox, 0, wx.ALIGN_RIGHT | wx.TOP, 15) panel.SetSizerAndFit(box) panel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) title.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) def OnLooseFocus(self, event): if CeciliaLib.getVar("canGrabFocus"): win = wx.FindWindowAtPointer() if win[0] is not None: if win[0].GetTopLevelParent() == self: pass else: win = CeciliaLib.getVar("interface") win.Raise() def OnClose(self): self.Hide() def onDistribution(self, ind, label): if label == 'Sine': self.ptsSlider.Enable() self.widthSlider.Disable() elif label == 'Sawtooth': self.ptsSlider.Disable() self.widthSlider.Disable() elif label == 'Square': self.ptsSlider.Disable() self.widthSlider.Enable() elif label == 'Triangle': self.ptsSlider.Disable() self.widthSlider.Enable() elif label == 'Sinc': self.ptsSlider.Enable() self.widthSlider.Disable() elif label == 'Pulse': self.ptsSlider.Enable() self.widthSlider.Enable() elif label == 'Bi-Pulse': self.ptsSlider.Enable() self.widthSlider.Enable() def OnApply(self): dist = self.distMenu.getLabel() points = self.ptsSlider.GetValue() amp = self.ampSlider.GetValue() freq = self.freqSlider.GetValue() phase = self.phaseSlider.GetValue() width = self.widthSlider.GetValue() if dist == 'Sine': dict = self.sineGenerate(points, amp, freq, phase) elif dist == 'Square': dict = self.squareGenerate(points, amp, freq, phase, width) elif dist == 'Triangle': dict = self.triangleGenerate(points, amp, freq, phase, width) elif dist == 'Sawtooth': dict = self.sawtoothGenerate(points, amp, freq, phase) elif dist == 'Sinc': dict = self.sincGenerate(points, amp, freq, phase) elif dist == 'Pulse': dict = self.pulseGenerate(points, amp, freq, phase, width) elif dist == 'Bi-Pulse': dict = self.bipulseGenerate(points, amp, freq, phase, width) line = CeciliaLib.getVar("grapher").plotter.getLine(CeciliaLib.getVar("grapher").plotter.getSelected()) line.setLineState(dict) line.setShow(1) CeciliaLib.getVar("grapher").toolbar.toolbox.setShow(1) if line.getSlider() is not None: line.getSlider().setPlay(1) CeciliaLib.getVar("grapher").plotter.draw() def sineGenerate(self, points, amp, freq, phase): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / (points - 1) A = amp * .5 w = 2 * math.pi * freq ph = phase * 2 * math.pi if addPointsBefore: templist.extend(addPointsBefore) for i in range(points): inc = i * step x = inc * (maxx - minx) + minx y = A * math.sin(w * inc + ph) + .5 templist.append([x, y]) if addPointsAfter: templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def squareGenerate(self, points, amp, freq, phase, width): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / freq A = amp * .5 if int(freq) == freq: length = int(freq) else: length = int(freq) + 1 if phase >= .5: A = -A if addPointsBefore: templist.extend(addPointsBefore) for i in range(length): inc = i * step x = inc * (maxx - minx) + minx y = .5 + A templist.append([x, y]) x = inc * (maxx - minx) + minx + (step * (maxx - minx) * width) if x > 1: x = 1 templist.append([x, y]) break else: templist.append([x, y]) x = inc * (maxx - minx) + minx + (step * (maxx - minx) * width) y = .5 - A templist.append([x, y]) x = (i + 1) * step * (maxx - minx) + minx if x > 1: x = 1 templist.append([x, y]) break else: templist.append([x, y]) if addPointsAfter: templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def triangleGenerate(self, points, amp, freq, phase, width): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / freq A = amp * .5 if int(freq) == freq: length = int(freq) else: length = int(freq) + 1 if phase >= .5: A = -A if addPointsBefore: templist.extend(addPointsBefore) for i in range(length): inc = i * step x = inc * (maxx - minx) + minx y = .5 - A templist.append([x, y]) x = inc * (maxx - minx) + minx + (step * (maxx - minx) * width) y = .5 + A if x > maxx: y = .5 - (A * step * maxx / x * width) x = maxx templist.append([x, y]) if x < maxx: y = (A * step * width * maxx / x) if A <= 0.0: y += 1 x = maxx templist.append([x, y]) if addPointsAfter: templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def sawtoothGenerate(self, points, amp, freq, phase): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / freq A = amp * .5 if int(freq) == freq: length = int(freq) else: length = int(freq) + 1 if phase >= .5: A = -A if addPointsBefore: templist.extend(addPointsBefore) for i in range(length): inc = i * step x = inc * (maxx - minx) + minx y = .5 + A templist.append([x, y]) x = (i + 1) * step * (maxx - minx) + minx y = .5 - A if x > 1: gap = step - (x - 1) if A >= 0: y = y + gap else: y = y - (y * gap) x = 1 templist.append([x, y]) break else: templist.append([x, y]) if addPointsAfter: templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def sincGenerate(self, points, amp, freq, phase): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] step = 1. / (points - 1) A = amp * .5 half = points / 2 ph = phase * 2 - 1 if addPointsBefore: templist.extend(addPointsBefore) for i in range(points): inc = i * step x = inc * (maxx - minx) + minx scl = float(i - half - half * ph) / half * freq if scl == 0.0: y = A + 0.5 else: y = A * math.sin(scl) / scl + .5 templist.append([x, y]) if addPointsAfter: templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def pulseGenerate(self, points, amp, freq, phase, width): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] twopi = math.pi * 2 step = 1. / (points - 1) A = amp * .25 numh = math.floor(width * 96 + 4) if math.fmod(numh, 2.0) == 0.0: numh += 1 finc = freq * 0.5 / points pointer = phase * 0.5 if addPointsBefore: templist.extend(addPointsBefore) for i in range(points): inc = i * step x = inc * (maxx - minx) + minx y = A * (math.tan(pow(math.fabs(math.sin(twopi * pointer)), numh))) + .5 templist.append([x, y]) pointer += finc if pointer < 0: pointer += 1.0 elif pointer >= 1: pointer -= 1.0 if addPointsAfter: templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def bipulseGenerate(self, points, amp, freq, phase, width): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) templist = [] twopi = math.pi * 2 step = 1. / (points - 1) A = amp * .25 numh = math.floor(width * 96 + 4) if math.fmod(numh, 2.0) == 0.0: numh += 1 finc = freq * 0.5 / points pointer = phase * 0.5 if addPointsBefore: templist.extend(addPointsBefore) for i in range(points): inc = i * step x = inc * (maxx - minx) + minx y = A * (math.tan(pow(math.sin(twopi * pointer), numh))) + .5 templist.append([x, y]) pointer += finc if pointer < 0: pointer += 1.0 elif pointer >= 1: pointer -= 1.0 if addPointsAfter: templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} class ProcessFrame(wx.Frame): def __init__(self, parent): style = (wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_FLOAT_ON_PARENT) wx.Frame.__init__(self, parent, title='', style=style) self.SetBackgroundColour(BACKGROUND_COLOUR) self.parent = parent self.SetClientSize((300, 240)) self.distList = ['Scatter', 'Jitter', 'Comp/Expand', 'Smoother'] self.interpList = ['Linear', 'Sample hold'] self._oldState = None self._oldSelected = None panel = wx.Panel(self, -1, style=wx.BORDER_SIMPLE) w, h = self.GetSize() panel.SetBackgroundColour(BACKGROUND_COLOUR) box = wx.BoxSizer(wx.VERTICAL) title = FrameLabel(panel, "FUNCTION PROCESSOR", size=(w - 2, 24)) box.Add(title, 0, wx.ALL, 1) slidersBox = wx.FlexGridSizer(5, 2, 5, 5) interpBox = wx.BoxSizer(wx.HORIZONTAL) interpLabel = wx.StaticText(panel, -1, "Interpolation") interpLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) interpLabel.SetForegroundColour(WHITE_COLOUR) interpBox.Add(interpLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 113) self.interpMenu = CustomMenu(panel, self.interpList, self.interpList[0]) CeciliaLib.setToolTip(self.interpMenu, TT_STOCH_INTERP) interpBox.Add(self.interpMenu, 0, wx.LEFT | wx.RIGHT, 5) ptsLabel = wx.StaticText(panel, -1, "Points") ptsLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) ptsLabel.SetForegroundColour(WHITE_COLOUR) self.ptsSlider = ControlSlider(panel, 5, 1000, 50, size=(225, 15), integer=True, backColour=BACKGROUND_COLOUR) self.ptsSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.ptsSlider, TT_STOCH_POINTS) slidersBox.AddMany([(ptsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.ptsSlider, 0, wx.RIGHT, 5)]) self.scatXLabel = wx.StaticText(panel, -1, "Scatt X") self.scatXLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) self.scatXLabel.SetForegroundColour(WHITE_COLOUR) self.scatXSlider = ControlSlider(panel, 0, 0.5, 0.005, size=(225, 15), backColour=BACKGROUND_COLOUR) self.scatXSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.scatXSlider, TT_SCATTER_X) slidersBox.AddMany([(self.scatXLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.scatXSlider, 0, wx.RIGHT, 5)]) self.scatYLabel = wx.StaticText(panel, -1, "Scatt Y") self.scatYLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) self.scatYLabel.SetForegroundColour(WHITE_COLOUR) self.scatYSlider = ControlSlider(panel, 0, 0.5, 0.05, size=(225, 15), backColour=BACKGROUND_COLOUR) self.scatYSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.scatYSlider, TT_SCATTER_Y) slidersBox.AddMany([(self.scatYLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.scatYSlider, 0, wx.RIGHT, 5)]) offXLabel = wx.StaticText(panel, -1, "Offset X") offXLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) offXLabel.SetForegroundColour("#FFFFFF") self.offXSlider = ControlSlider(panel, -0.5, 0.5, 0, size=(225, 15), backColour=BACKGROUND_COLOUR) self.offXSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.offXSlider, TT_OFFSET_X) slidersBox.AddMany([(offXLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.offXSlider, 0, wx.RIGHT, 5)]) offYLabel = wx.StaticText(panel, -1, "Offset Y") offYLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) offYLabel.SetForegroundColour("#FFFFFF") self.offYSlider = ControlSlider(panel, -0.5, 0.5, 0, size=(225, 15), backColour=BACKGROUND_COLOUR) self.offYSlider.setSliderHeight(10) CeciliaLib.setToolTip(self.offYSlider, TT_OFFSET_Y) slidersBox.AddMany([(offYLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5), (self.offYSlider, 0, wx.RIGHT, 5)]) distBox = wx.BoxSizer(wx.HORIZONTAL) distLabel = wx.StaticText(panel, -1, "Processor") distLabel.SetFont(wx.Font(TEXT_LABELFORWIDGET_FONT, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) distLabel.SetForegroundColour(WHITE_COLOUR) distBox.Add(distLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 128) self.distMenu = CustomMenu(panel, self.distList, self.distList[0], outFunction=self.onDistribution) CeciliaLib.setToolTip(self.distMenu, TT_PROC_TYPE) self.distMenu.setLabel(self.distMenu.getLabel(), True) distBox.Add(self.distMenu, 0, wx.LEFT | wx.RIGHT, 5) applyBox = wx.BoxSizer(wx.HORIZONTAL) apply = ApplyToolBox(panel, outFunction=[self.OnClose, self.OnApply]) applyBox.Add(apply, 0, wx.RIGHT, 8) box.Add(distBox, 0, wx.ALL, 5) box.Add(interpBox, 0, wx.ALL, 5) box.Add(slidersBox, 0, wx.EXPAND | wx.ALL, 5) box.Add(applyBox, 0, wx.ALIGN_RIGHT | wx.TOP, 15) panel.SetSizerAndFit(box) panel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) title.Bind(wx.EVT_LEAVE_WINDOW, self.OnLooseFocus) def OnLooseFocus(self, event): if CeciliaLib.getVar("canGrabFocus"): win = wx.FindWindowAtPointer() if win[0] is not None: if win[0].GetTopLevelParent() == self: pass else: win = CeciliaLib.getVar("interface") win.Raise() def OnClose(self): self._oldState = None self.Hide() def onDistribution(self, ind, label): if label == 'Scatter': self.ptsSlider.Disable() self.scatYSlider.Enable() self.offXSlider.Disable() self.offYSlider.Disable() self.scatXLabel.SetLabel('Scatt X') self.scatYLabel.SetLabel('Scatt Y') self.scatXSlider.SetRange(0, 0.5) self.scatXSlider.SetValue(0.005) self.scatYSlider.SetRange(0, 0.5) self.scatYSlider.SetValue(0.05) elif label == 'Jitter': self.ptsSlider.Enable() self.scatYSlider.Enable() self.offXSlider.Disable() self.offYSlider.Disable() self.scatXLabel.SetLabel('Jitte X') self.scatYLabel.SetLabel('Jitte Y') self.scatXSlider.SetRange(0, 0.5) self.scatXSlider.SetValue(0.005) self.scatYSlider.SetRange(0, 0.5) self.scatYSlider.SetValue(0.05) elif label == 'Comp/Expand': self.ptsSlider.Disable() self.scatYSlider.Enable() self.offXSlider.Enable() self.offYSlider.Enable() self.scatXLabel.SetLabel('Comp X') self.scatYLabel.SetLabel('Comp Y') self.scatXSlider.SetRange(0., 2.) self.scatXSlider.SetValue(1.) self.scatYSlider.SetRange(0., 2.) self.scatYSlider.SetValue(1.) elif label == 'Smoother': self.ptsSlider.Disable() self.scatYSlider.Disable() self.offXSlider.Disable() self.offYSlider.Disable() self.scatXLabel.SetLabel('Smooth') self.scatYLabel.SetLabel('Comp Y') self.scatXSlider.SetRange(0., 1.) self.scatXSlider.SetValue(0.5) self.scatYSlider.SetRange(0., 2.) self.scatYSlider.SetValue(1.) self._oldState = None def OnApply(self): line = CeciliaLib.getVar("grapher").plotter.getSelected() if self._oldSelected != line or self._oldState is None: self.data = CeciliaLib.getVar("grapher").plotter.getLine(CeciliaLib.getVar("grapher").plotter.getSelected()).getLineState()['data'] self._oldSelected = line self._oldState = self.data elif self._oldSelected == line and self._oldState is not None: self.data = self._oldState dist = self.distMenu.getLabel() interp = self.interpMenu.getLabel() points = self.ptsSlider.GetValue() scatX = self.scatXSlider.GetValue() scatY = self.scatYSlider.GetValue() offX = self.offXSlider.GetValue() offY = self.offYSlider.GetValue() if dist == 'Scatter': dict = self.processScattering(interp, scatX, scatY) elif dist == 'Jitter': dict = self.processJittering(interp, points, scatX, scatY) elif dist == 'Comp/Expand': dict = self.processCompExpand(dist, points, scatX, scatY, offX, offY) elif dist == 'Smoother': dict = self.processSmoother(dist, points, scatX) line = CeciliaLib.getVar("grapher").plotter.getLine(CeciliaLib.getVar("grapher").plotter.getSelected()) line.setLineState(dict) line.setShow(1) CeciliaLib.getVar("grapher").toolbar.toolbox.setShow(1) if line.getSlider() is not None: line.getSlider().setPlay(1) CeciliaLib.getVar("grapher").plotter.draw() def processScattering(self, interp, scatX, scatY): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) data = CeciliaLib.deepCopy(self.data) dataLen = len(data) step = 1. / dataLen templist = [] if addPointsBefore or addPointsAfter: templist.extend(addPointsBefore) istart, istop = selected[0], selected[-1] else: istart, istop = 0, dataLen - 1 for i in range(istart, istop): x = data[i][0] y = data[i][1] lastY = y if i == 0: newX = x else: newX = x + (random.uniform(-scatX, scatX) * step) if newX < 0: newX = 0 elif newX > 1: newX = 1 try: if newX < templist[-1][0]: newX = templist[-1][0] except: pass newY = y + random.uniform(-scatY, scatY) if newY < 0: newY = 0 elif newY > 1: newY = 1 if interp == 'Sample hold': if i != 0: templist.append([newX, templist[-1][1]]) else: templist.append([newX, lastY]) lastY = newY templist.append([newX, newY]) if addPointsAfter: if interp == 'Sample hold': templist.append([addPointsAfter[0][0], lastY]) templist.extend(addPointsAfter) else: if interp == 'Sample hold': templist.append([data[-1][0], lastY]) else: templist.append(data[-1]) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def processJittering(self, interp, points, scatX, scatY): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) data = CeciliaLib.deepCopy(self.data) dataLen = len(data) templist = [] if addPointsBefore or addPointsAfter: templist.extend(addPointsBefore) istart, istop = selected[0], selected[-1] if istop >= dataLen: istart -= len(templist) istop -= len(templist) totalLen = data[istop][0] - data[istart][0] else: istart, istop = 0, dataLen - 1 totalLen = 1 for i in range(istart, istop): x = data[i][0] y = data[i][1] lastY = y x2 = data[i + 1][0] y2 = data[i + 1][1] distance = x2 - x numStep = int(points * distance / totalLen) if numStep == 0: continue step = distance / numStep for j in range(numStep): if j == 0 and i == 0: newX = x else: newX = x + ((step * j) + (random.uniform(-scatX, scatX) * step)) if newX < 0: newX = 0 elif newX > 1: newX = 1 try: if newX < templist[-1][0]: newX = templist[-1][0] except: pass ydiff = y2 - y if ydiff == 0: newY = y + random.uniform(-scatY, scatY) else: newY = (y + ((y2 - y) / numStep * j)) + random.uniform(-scatY, scatY) if newY < 0: newY = 0 elif newY > 1: newY = 1 if interp == 'Sample hold': if j == 0 and i != 0: templist.append([newX, templist[-1][1]]) else: templist.append([newX, lastY]) lastY = newY templist.append([newX, newY]) if addPointsAfter: if interp == 'Sample hold': templist.append([addPointsAfter[0][0], lastY]) templist.extend(addPointsAfter) else: if interp == 'Sample hold': templist.append([data[-1][0], lastY]) else: templist.append(data[-1]) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def processCompExpand(self, dist, points, scatX, scatY, offX, offY): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) data = CeciliaLib.deepCopy(self.data) dataLen = len(data) templist = [] if addPointsBefore or addPointsAfter: templist.extend(addPointsBefore) istart, istop = selected[0], selected[-1] else: istart, istop = 0, dataLen ylist = [d[1] for d in data][istart: istop] minY = min(ylist) maxY = max(ylist) midY = (maxY - minY) * 0.5 + minY if addPointsBefore: if scatX != 1.0 or offX != 0.0: templist.append(data[selected[0]]) else: if scatX != 1.0 or offX != 0.0: templist.append(data[0]) for i in range(istart, istop): x = data[i][0] y = data[i][1] newX = 0.5 + (x - 0.5) * scatX + offX if newX < 0: newX = 0. elif newX > 1: newX = 1. try: if newX < templist[-1][0]: newX = templist[-1][0] except: pass newY = midY + (y - midY) * scatY + offY if newY < 0: newY = 0. elif newY > 1: newY = 1. templist.append([newX, newY]) if addPointsAfter: if scatX != 1.0 or offX != 0.0: templist.append([addPointsAfter[0][0], templist[-1][1]]) templist.extend(addPointsAfter) else: if scatX != 1.0 or offX != 0.0: templist[0] = [0.0, templist[1][1]] templist.append([1.0, templist[-1][1]]) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} def processSmoother(self, dist, points, scatX): selected = CeciliaLib.getVar("grapher").plotter.selectedPoints minx, maxx, addPointsBefore, addPointsAfter = self.parent.checkForSelection(selected) data = CeciliaLib.deepCopy(self.data) dataLen = len(data) templist = [] if addPointsBefore or addPointsAfter: templist.extend(addPointsBefore) istart, istop = selected[0], selected[-1] else: istart, istop = 0, dataLen last = data[istart][1] for i in range(istart, istop): x = data[i][0] y = data[i][1] newY = y + (last - y) * scatX if newY < 0: newY = 0. elif newY > 1: newY = 1. last = newY templist.append([x, newY]) if addPointsAfter: templist.extend(addPointsAfter) CeciliaLib.getVar("grapher").plotter.resetSelectedPoints() return {'data': templist} #--------------------------- # Transport # -------------------------- class Transport(wx.Panel): def __init__(self, parent, size=(90, 30), outPlayFunction=None, outRecordFunction=None, backgroundColour=None, borderColour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) if backgroundColour: self.backgroundColour = backgroundColour else: self.backgroundColour = BACKGROUND_COLOUR self.SetBackgroundColour(self.backgroundColour) if borderColour: self.borderColour = borderColour else: self.borderColour = BACKGROUND_COLOUR self.SetMaxSize(self.GetSize()) self.rectList = [] for i in range(2): self.rectList.append(wx.Rect(i * 45, 0, 40, self.GetSize()[1])) self.outPlayFunction = outPlayFunction self.outRecordFunction = outRecordFunction self.playOver = False self.recordOver = False self.playOverWait = True self.recordOverWait = True self.playColour = TR_PLAY_NORMAL_COLOUR self.recordColour = TR_RECORD_OFF_COLOUR self.playing = False self.recording = False self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) self.Bind(wx.EVT_LEFT_UP, self.MouseUp) CeciliaLib.setToolTip(self, TT_TRANSPORT) if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def setPlay(self, play): self.playing = play wx.CallAfter(self.Refresh) def getPlay(self): return self.playing def onPlay(self): if not self.playing: if self.getRecord(): self.setRecord(False) def setRecord(self, record): self.recording = record if not self.recording: self.recordColour = TR_RECORD_OFF_COLOUR else: self.recordColour = TR_RECORD_ON_COLOUR wx.CallAfter(self.Refresh) def getRecord(self): return self.recording def onRecord(self): if self.recording: if not self.getPlay(): self.setPlay(True) else: if self.getPlay(): self.setPlay(False) def OnMotion(self, event): pos = event.GetPosition() if self.rectList[0].Contains(pos) and self.playOverWait: self.rewindOver = False self.playOver = True self.recordOver = False elif self.rectList[1].Contains(pos) and self.recordOverWait: self.rewindOver = False self.playOver = False self.recordOver = True self.checkForOverReady(pos) wx.CallAfter(self.Refresh) event.Skip() def setOverWait(self, which): if which == 0: self.playOverWait = False elif which == 1: self.recordOverWait = False def checkForOverReady(self, pos): if not self.rectList[0].Contains(pos): self.playOverWait = True if not self.rectList[1].Contains(pos): self.recordOverWait = True def MouseDown(self, event): pos = event.GetPosition() for i, rec in enumerate(self.rectList): if rec.Contains(pos): if i == 0: self.playColour = TR_PLAY_CLICK_COLOUR self.playOver = False elif i == 1 and not self.playing: if not self.recording: self.recording = True self.recordColour = TR_RECORD_ON_COLOUR else: self.recording = False self.recordColour = TR_RECORD_OFF_COLOUR self.recordOver = False self.onRecord() if self.outRecordFunction: self.outRecordFunction(self.recording) self.setOverWait(i) break wx.CallAfter(self.Refresh) event.Skip() def MouseUp(self, event): pos = event.GetPosition() for i, rec in enumerate(self.rectList): if rec.Contains(pos): if i == 0: if not self.playing: self.playing = True else: self.playing = False self.playColour = TR_PLAY_NORMAL_COLOUR self.playOver = False self.onPlay() if self.outPlayFunction: self.outPlayFunction(self.playing) break wx.CallAfter(self.Refresh) def OnLeave(self, event): self.playOver = False self.recordOver = False self.playOverWait = True self.recordOverWait = True wx.CallAfter(self.Refresh) event.Skip() def OnPaint(self, event): w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(self.backgroundColour, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(self.backgroundColour, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) # Draw play/stop if self.playOver: offStopX = 13 offStopY = 8 offPlayX = 13 offPlayY = 7 else: offStopX = 14 offStopY = 9 offPlayX = 14 offPlayY = 9 x, y, w1, h1 = self.rectList[0].Get() rec = wx.Rect(x + 1, y + 1, w1 - 2, h1 - 2) gc.SetPen(wx.Pen(TR_BORDER_COLOUR, 1)) gc.SetBrush(wx.Brush(TR_BACK_COLOUR)) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2], rec[3], 4) gc.SetBrush(wx.Brush(self.playColour, wx.SOLID)) if not self.playing: tri = [(x + offPlayX, y + offPlayY), (x + offPlayX, h1 - offPlayY), (x + w1 - offPlayX, h1 / 2), (x + offPlayX, y + offPlayY)] gc.DrawLines(tri) else: gc.DrawRoundedRectangle(x + offStopX, y + offStopY, w1 - (offStopX * 2), h1 - (offStopY * 2), 3) # Draw record if self.recordOver and not self.playing: radius = 7 else: radius = 6 x, y, w1, h1 = self.rectList[1].Get() rec = wx.Rect(x + 1, y + 1, w1 - 2, h1 - 2) gc.SetPen(wx.Pen(TR_BORDER_COLOUR, 1)) gc.SetBrush(wx.Brush(TR_BACK_COLOUR)) gc.DrawRoundedRectangle(rec[0], rec[1], rec[2], rec[3], 4) gc.SetBrush(wx.Brush(self.recordColour, wx.SOLID)) gc.DrawEllipse(x + (w1 / 2) - radius, h1 / 2 - radius, radius * 2, radius * 2) #--------------------------- # VuMeter # -------------------------- class VuMeter(wx.Panel): def __init__(self, parent, size=(218, 11)): wx.Panel.__init__(self, parent, -1, size=size) self.parent = parent self.SetMinSize((218, 6)) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour("#000000") self.Bind(wx.EVT_PAINT, self.OnPaint) self.nchnls = CeciliaLib.getVar("nchnls") self.SetSize((218, 5 * self.nchnls + 1)) self.SetMaxSize((218, 5 * self.nchnls + 1)) self.bitmap = CeciliaLib.getVar("ICON_VUMETER") self.backBitmap = CeciliaLib.getVar("ICON_VUMETER_DARK") self.amplitude = [0] * self.nchnls self.oldChnls = 1 self.peak = 0 def updateNchnls(self): self.nchnls = CeciliaLib.getVar("nchnls") self.amplitude = [0] * self.nchnls gap = (self.nchnls - self.oldChnls) * 5 self.oldChnls = self.nchnls parentSize = self.parent.GetSize() self.SetSize((218, 5 * self.nchnls + 1)) self.SetMinSize((218, 5 * self.nchnls + 1)) self.SetMaxSize((218, 5 * self.nchnls + 1)) self.parent.SetSize((parentSize[0], parentSize[1] + gap)) wx.CallAfter(self.Refresh) if CeciliaLib.getVar("interface") is not None: CeciliaLib.getVar("interface").Layout() def setRms(self, *args): if args[0] < 0: return if not args: self.amplitude = [0 for i in range(self.nchnls)] else: self.amplitude = args self.amplitude = [math.log10(amp + 0.00001) * 0.2 + 1. for amp in self.amplitude] if self.seekPeak(): CeciliaLib.getControlPanel().updatePeak(self.peak) wx.CallAfter(self.Refresh) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush("#000000")) dc.Clear() dc.DrawRectangle(0, 0, w, h) for i in range(self.nchnls): try: width = int(self.amplitude[i] * w) except: width = 0 dc.DrawBitmap(self.backBitmap, 0, i * 5) if width > 0: dc.SetClippingRegion(0, i * 5, width, 5) dc.DrawBitmap(self.bitmap, 0, i * 5) dc.DestroyClippingRegion() def reset(self): self.amplitude = [0 for i in range(self.nchnls)] wx.CallAfter(self.Refresh) def seekPeak(self): newPeak = False if max(self.amplitude) > self.peak: self.peak = max(self.amplitude) newPeak = True return newPeak def getPeak(self): return self.peak def resetMax(self): self.peak = 0 class TabsPanel(wx.Panel): def __init__(self, parent, size=(230, 20), outFunction=None, backgroundColour=None, borderColour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) if backgroundColour: self.backgroundColour = backgroundColour else: self.backgroundColour = BACKGROUND_COLOUR self.SetBackgroundColour(self.backgroundColour) if borderColour: self.borderColour = borderColour else: self.borderColour = BACKGROUND_COLOUR self.SetMaxSize(self.GetSize()) self.outFunction = outFunction self.font = self.GetFont() self.font.SetPointSize(TAB_TITLE_FONT) self.rects = [wx.Rect(0, 0, 117, 20), wx.Rect(113, 0, 117, 20)] self.choices = ["In/Out", "Post-Proc"] self.selected = "In/Out" self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) if CeciliaLib.getVar("systemPlatform") == "win32": self.dcref = wx.BufferedPaintDC else: self.dcref = wx.PaintDC def MouseDown(self, event): pos = event.GetPosition() for i, rect in enumerate(self.rects): if rect.Contains(pos): self.selected = self.choices[i] break self.outFunction(i) wx.CallAfter(self.Refresh) event.Skip() def OnPaint(self, event): def draw(which): index = self.choices.index(which) if which == self.selected: pen = wx.Pen(TITLE_BACK_COLOUR, 1) brush = wx.Brush(TITLE_BACK_COLOUR) else: pen = wx.Pen(TR_BORDER_COLOUR, 1) brush = wx.Brush(BACKGROUND_COLOUR) gc.SetPen(pen) gc.SetBrush(brush) x, y, x1, y1 = self.rects[index][0] + 1, self.rects[index][1], self.rects[index][2] - 2, self.rects[index][3] poly = [(x, y1), (x + 5, y), (x + x1 - 5, y), (x + x1, y1)] gc.DrawLines(poly) dc.DrawLabel(which, self.rects[index], wx.ALIGN_CENTER) w, h = self.GetSize() dc = self.dcref(self) gc = wx.GraphicsContext_Create(dc) dc.SetBrush(wx.Brush(self.backgroundColour, wx.SOLID)) dc.Clear() dc.SetPen(wx.Pen(self.backgroundColour, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) dc.SetFont(self.font) dc.SetTextForeground(WHITE_COLOUR) choices = [x for x in self.choices] choices.remove(self.selected) choices.append(self.selected) for choice in choices: draw(choice) #--------------------------- # Input Mode Button (return 0, 1, 2 or 3) # -------------------------- class InputModeButton(wx.Panel): def __init__(self, parent, state, size=(20, 20), outFunction=None, colour=None): wx.Panel.__init__(self, parent, -1, size=size) self.SetMaxSize(self.GetSize()) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.SetBackgroundColour(BACKGROUND_COLOUR) self.outFunction = outFunction self.state = state if colour: self.colour = colour else: self.colour = BACKGROUND_COLOUR self.bitmaps = [ICON_INPUT_1_FILE.GetBitmap(), ICON_INPUT_2_LIVE.GetBitmap(), ICON_INPUT_3_MIC.GetBitmap(), ICON_INPUT_4_MIC_RECIRC.GetBitmap()] self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown) CeciliaLib.setToolTip(self, TT_INPUT_MODE) def OnPaint(self, event): w, h = self.GetSize() dc = wx.AutoBufferedPaintDC(self) dc.SetBrush(wx.Brush(BACKGROUND_COLOUR, wx.SOLID)) dc.Clear() # Draw background dc.SetPen(wx.Pen(BACKGROUND_COLOUR, width=0, style=wx.SOLID)) dc.DrawRectangle(0, 0, w, h) dc.DrawBitmap(self.bitmaps[self.state], 0, 0, True) def MouseDown(self, event): self.state = (self.state + 1) % 4 if self.outFunction: self.outFunction(self.state) wx.CallAfter(self.Refresh) event.Skip() def getValue(self): return self.state def setValue(self, value): self.state = value wx.CallAfter(self.Refresh) class Separator(wx.Panel): def __init__(self, parent, size=(200, 1), style=wx.BORDER_NONE, colour=BORDER_COLOUR): wx.Panel.__init__(self, parent, size=size, style=style) self.SetBackgroundColour(colour) class Knob(wx.Panel): def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=(26, 26), style=wx.TAB_TRAVERSAL, outFunction=None): wx.Panel.__init__(self, parent, id, pos, size, style) self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_MOTION, self.OnMotion) self.outFunction = outFunction self.twopi = math.pi * 2 self.anchor1 = math.pi * (4.5 / 6) self.anchor2 = math.pi * (1.5 / 6) self.ancrange = self.twopi - self.anchor1 + self.anchor2 self.startpos = None self.value = 0.5 self.tempval = 0.5 self.diff = 0 self.inc = 0.005 def OnLeftDown(self, evt): self.startpos = evt.GetPosition() if evt.ShiftDown(): self.inc = 0.0001 else: self.inc = 0.005 self.CaptureMouse() def OnLeftUp(self, evt): if self.HasCapture(): self.value = self.tempval self.ReleaseMouse() def OnMotion(self, evt): pos = evt.GetPosition() if self.HasCapture(): vert = (pos[1] - self.startpos[1]) * -self.inc self.diff = (pos[0] - self.startpos[0]) * self.inc + vert wx.CallAfter(self.Refresh) def OnPaint(self, evt): w,h = self.GetSize() dc = wx.BufferedPaintDC(self) gc = wx.GraphicsContext.Create(dc) dc.SetBrush(wx.Brush("#FFFFFF", style=wx.TRANSPARENT)) dc.SetPen(wx.Pen("#FFFFFF", width=1, style=wx.TRANSPARENT)) dc.Clear() self.tempval = self.value + self.diff if self.tempval < 0: self.tempval = 0 elif self.tempval > 1: self.tempval = 1 anchor2 = self.tempval * self.ancrange + self.anchor1 if anchor2 > (self.twopi): anchor2 -= self.twopi gc.SetBrush(wx.Brush("#FFFFFF", style=wx.TRANSPARENT)) font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) gc.SetFont(font, "#222222") path = gc.CreatePath() gc.SetPen(wx.Pen("#AAAAAA", 2)) path.AddArc(w/2, h/2+2, 12, self.anchor2, self.anchor1, False) gc.DrawPath(path) path = gc.CreatePath() gc.SetPen(wx.Pen("#000000", 2)) path.AddArc(w/2, h/2+2, 12, anchor2, self.anchor1, False) gc.DrawPath(path) if self.outFunction is not None: self.outFunction(self.tempval) class SpectrumFrame(wx.Frame): def __init__(self, parent, title="Frequency Spectrum", pos=(50, 50), size=(600, 400)): wx.Frame.__init__(self, parent, -1, title, pos, size, style=wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX | wx.FRAME_FLOAT_ON_PARENT) self.panel = wx.Panel(self) mainsizer = wx.BoxSizer(wx.HORIZONTAL) spectrum = self.createSpectrum() mainsizer.Add(spectrum, 1, wx.ALL|wx.EXPAND, 5) self.panel.SetSizerAndFit(mainsizer) def onClose(self): CeciliaLib.setVar('spectrumFrame', None) self.Destroy() def createSpectrum(self): sizer = wx.BoxSizer(wx.VERTICAL) toolbox = wx.BoxSizer(wx.HORIZONTAL) self.specFreq = wx.ToggleButton(self.panel, -1, label="Freq Log") self.specFreq.SetValue(0) self.specFreq.Bind(wx.EVT_TOGGLEBUTTON, self.specFreqScale) toolbox.Add(self.specFreq, 1, wx.TOP|wx.LEFT, 4) self.specMag = wx.ToggleButton(self.panel, -1, label="Mag Log") self.specMag.SetValue(1) self.specMag.Bind(wx.EVT_TOGGLEBUTTON, self.specMagScale) toolbox.Add(self.specMag, 1, wx.TOP|wx.LEFT, 4) winchoices = ["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3-term", "Blackman-Harris 4", "Blackman-Harris 7", "Tuckey", "Half-sine"] self.specWin = wx.Choice(self.panel, -1, choices=winchoices) self.specWin.SetSelection(2) self.specWin.Bind(wx.EVT_CHOICE, self.specWinType) toolbox.Add(self.specWin, 1, wx.TOP|wx.LEFT, 4) sizechoices = ["64", "128", "256", "512", "1024", "2048", "4096", "8192"] self.specSize = wx.Choice(self.panel, -1, choices=sizechoices) self.specSize.SetSelection(4) self.specSize.Bind(wx.EVT_CHOICE, self.specSetSize) toolbox.Add(self.specSize, 1, wx.TOP|wx.LEFT, 4) self.specAmp = Knob(self.panel, outFunction=self.specSetAmp) # self.specAmp.SetBackgroundColour(APP_BACKGROUND_COLOUR) toolbox.Add(self.specAmp, 0.1, wx.TOP|wx.LEFT|wx.RIGHT, 6) sizer.Add(toolbox, 0, wx.EXPAND) self.spectrum = PyoGuiSpectrum(parent=self.panel, mscaling=1) self.zoomH = HRangeSlider(self.panel, minvalue=0, maxvalue=0.5, valtype='float', function=self.specZoom) sizer.Add(self.spectrum, 1, wx.LEFT | wx.TOP | wx.EXPAND, 5) sizer.Add(self.zoomH, 0, wx.EXPAND|wx.LEFT, 5) return sizer def setAnalyzer(self, obj): self.spectrum.setAnalyzer(obj) def specFreqScale(self, evt): self.spectrum.setFscaling(evt.GetInt()) def specMagScale(self, evt): self.spectrum.setMscaling(evt.GetInt()) def specWinType(self, evt): self.spectrum.obj.wintype = evt.GetInt() def specSetSize(self, evt): self.spectrum.obj.size = 1 << (evt.GetInt() + 6) def specZoom(self, values): self.spectrum.setLowFreq(self.spectrum.obj.setLowbound(values[0])) self.spectrum.setHighFreq(self.spectrum.obj.setHighbound(values[1])) def specSetAmp(self, value): value = rescale(value, ymin=0.0625, ymax=16, ylog=True) self.spectrum.obj.setGain(value) cecilia5-5.4.1/Resources/__init__.py000066400000000000000000000013651372272363700173030ustar00rootroot00000000000000# encoding: utf-8 """ Copyright 2011 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ cecilia5-5.4.1/Resources/audio.py000077500000000000000000002472651372272363700166630ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx import os, math, copy, time, traceback import Resources.CeciliaLib as CeciliaLib from .constants import * from .API_interface import * if CeciliaLib.getVar("samplePrecision") == '64 bit': from pyo64 import * else: from pyo import * class CeciliaFilein: def __init__(self, parent, name): self.parent = parent self.name = name info = CeciliaLib.getVar("userInputs")[name] chnls = CeciliaLib.getVar("nchnls") for filein in CeciliaLib.getControlPanel().cfileinList: if filein.name == name: break offset = filein.getOffset() mode = filein.getMode() if mode == 0: snd_chnls = info['nchnls' + self.name] if snd_chnls == 1: self.table = SndTable([info['path'] for i in range(chnls)], start=info["off" + self.name]) else: self.table = SndTable(info['path'], start=info["off" + self.name]) else: self.table = NewTable(length=offset, chnls=chnls, feedback=0.0) self.livein = Input(chnl=[x for x in range(chnls)], mul=0.7) self.filltabrec = TableRec(self.livein, self.table, fadetime=0.05).play() def sig(self): return self.table class CeciliaSampler: def __init__(self, parent, name, user_pitch, user_amp): self.type = "sampler" self.parent = parent self.baseModule = parent self.name = name self.user_pitch = user_pitch self.user_amp = user_amp info = CeciliaLib.getVar("userInputs")[name] totalTime = CeciliaLib.getVar("totalTime") chnls = CeciliaLib.getVar("nchnls") samplerList = CeciliaLib.getVar("userSamplers") for sampler in samplerList: if sampler.name == name: sinfo = sampler.getSamplerInfo() break self.sampler = sampler self.mode = self.sampler.mode self.start_play, self.start_midi = False, False self.dur_play, self.dur_midi = False, False self.xfade_play, self.xfade_midi = False, False self.gain_play, self.gain_midi = False, False self.pitch_play, self.pitch_midi = False, False if self.mode != 1: graph_lines = {} for line in CeciliaLib.getVar("grapher").plotter.getData(): if line.name.startswith(self.name): graph_lines[line.name] = line paths = [slider.getPath() for slider in sampler.getSamplerSliders()] ################ start ################ start_init, self.start_play, self.start_rec = sinfo['loopIn'][0], sinfo['loopIn'][1], sinfo['loopIn'][2] try: self.start_midi, start_midictl, start_midichnl = sinfo['loopIn'][3], sinfo['loopIn'][4], sinfo['loopIn'][5] self.start_mini, self.start_maxi = sinfo['loopIn'][6], sinfo['loopIn'][7] self.start_osc = sinfo['loopIn'][8] except: self.start_midi, start_midictl, start_midichnl, self.start_mini, self.start_maxi = False, None, 1, 0, 1 self.start_osc = None line = graph_lines[self.name + 'start'] curved = line.getCurved() if curved: self.start_table = CosTable() else: self.start_table = LinTable() if not self.start_play and not self.start_midi: self.start = SigTo(value=start_init, time=0.025, init=start_init) if self.start_play: data = line.getData() data = [tuple(x) for x in data] self.setGraph('start', data) self.start = TableRead(self.start_table, freq=1.0 / totalTime).play() elif self.start_midi: self.start = Midictl(start_midictl, self.start_mini, self.start_maxi, start_init, start_midichnl) elif self.start_osc is not None: self.baseModule._addOpenSndCtrlWidget(self.start_osc[0], self.start_osc[1], self, name='start') if self.start_rec: self.start_record = ControlRec(self.start, filename=paths[0], rate=1000, dur=totalTime).play() ################ dur ################ dur_init, self.dur_play, self.dur_rec = sinfo['loopOut'][0], sinfo['loopOut'][1], sinfo['loopOut'][2] try: self.dur_midi, dur_midictl, dur_midichnl = sinfo['loopOut'][3], sinfo['loopOut'][4], sinfo['loopOut'][5] self.dur_mini, self.dur_maxi = sinfo['loopOut'][6], sinfo['loopOut'][7] self.dur_osc = sinfo['loopOut'][8] except: self.dur_midi, dur_midictl, dur_midichnl, self.dur_mini, self.dur_maxi = False, None, 1, 0, 1 self.dur_osc = None line = graph_lines[self.name + 'end'] curved = line.getCurved() if curved: self.dur_table = CosTable() else: self.dur_table = LinTable() if not self.dur_play and not self.dur_midi: self.dur = SigTo(value=dur_init, time=0.025, init=dur_init) if self.dur_play: data = line.getData() data = [tuple(x) for x in data] self.setGraph('end', data) self.dur = TableRead(self.dur_table, freq=1.0 / totalTime).play() elif self.dur_midi: self.dur = Midictl(dur_midictl, self.dur_mini, self.dur_maxi, dur_init, dur_midichnl) elif self.dur_osc is not None: self.baseModule._addOpenSndCtrlWidget(self.dur_osc[0], self.dur_osc[1], self, name='dur') if self.dur_rec: self.dur_record = ControlRec(self.dur, filename=paths[1], rate=1000, dur=totalTime).play() ################ xfade ################ xfade_init, self.xfade_play, self.xfade_rec = sinfo['loopX'][0], sinfo['loopX'][1], sinfo['loopX'][2] try: self.xfade_midi, xfade_midictl, xfade_midichnl = sinfo['loopX'][3], sinfo['loopX'][4], sinfo['loopX'][5] self.xfade_osc = sinfo['loopX'][6] except: self.xfade_midi, xfade_midictl, xfade_midichnl = False, None, 1 self.xfade_osc = None line = graph_lines[self.name + 'xfade'] curved = line.getCurved() if curved: self.xfade_table = CosTable() else: self.xfade_table = LinTable() if not self.xfade_play and not self.xfade_midi: self.xfade = SigTo(value=xfade_init, time=0.025, init=xfade_init) if self.xfade_play: data = line.getData() data = [tuple(x) for x in data] self.setGraph('xfade', data) self.xfade = TableRead(self.xfade_table, freq=1.0 / totalTime).play() elif self.xfade_midi: self.xfade = Midictl(xfade_midictl, 0, 50, xfade_init, xfade_midichnl) elif self.xfade_osc is not None: self.baseModule._addOpenSndCtrlWidget(self.xfade_osc[0], self.xfade_osc[1], self, name='xfade') if self.xfade_rec: self.xfade_record = ControlRec(self.xfade, filename=paths[2], rate=1000, dur=totalTime).play() ################ gain ################ gain_init, self.gain_play, self.gain_rec = sinfo['gain'][0], sinfo['gain'][1], sinfo['gain'][2] try: self.gain_midi, gain_midictl, gain_midichnl = sinfo['gain'][3], sinfo['gain'][4], sinfo['gain'][5] self.gain_osc = sinfo['gain'][6] except: self.gain_midi, gain_midictl, gain_midichnl = False, None, 1 self.gain_osc = None line = graph_lines[self.name + 'gain'] curved = line.getCurved() if curved: self.gain_table = CosTable() else: self.gain_table = LinTable() if not self.gain_play and not self.gain_midi: self.gain_in = SigTo(value=gain_init, time=0.025, init=gain_init) if self.gain_play: data = line.getData() data = [tuple(x) for x in data] self.setGraph('gain', data) self.gain_in = TableRead(self.gain_table, freq=1.0 / totalTime).play() elif self.gain_midi: self.gain_in = Midictl(gain_midictl, -48, 18, gain_init, gain_midichnl) elif self.gain_osc is not None: self.baseModule._addOpenSndCtrlWidget(self.gain_osc[0], self.gain_osc[1], self, name='gain') if self.gain_rec: self.gain_record = ControlRec(self.gain_in, filename=paths[3], rate=1000, dur=totalTime).play() self.gain = Pow(10, self.gain_in * 0.05, mul=self.user_amp) ################ pitch ################ pitch_init, self.pitch_play, self.pitch_rec = sinfo['transp'][0], sinfo['transp'][1], sinfo['transp'][2] try: self.pitch_midi, pitch_midictl, pitch_midichnl = sinfo['transp'][3], sinfo['transp'][4], sinfo['transp'][5] self.pitch_osc = sinfo['transp'][6] except: self.pitch_midi, pitch_midictl, pitch_midichnl = False, None, 1 self.pitch_osc = None line = graph_lines[self.name + 'trans'] curved = line.getCurved() if curved: self.pitch_table = CosTable() else: self.pitch_table = LinTable() if not self.pitch_play and not self.pitch_midi: self.pitch_in = SigTo(value=pitch_init, time=0.025, init=pitch_init) if self.pitch_play: data = line.getData() data = [tuple(x) for x in data] self.setGraph('trans', data) self.pitch_in = TableRead(self.pitch_table, freq=1.0 / totalTime).play() elif self.pitch_midi: self.pitch_in = Midictl(pitch_midictl, -48, 48, pitch_init, pitch_midichnl) elif self.pitch_osc is not None: self.baseModule._addOpenSndCtrlWidget(self.pitch_osc[0], self.pitch_osc[1], self, name='pitch') if self.pitch_rec: self.pitch_record = ControlRec(self.pitch_in, filename=paths[4], rate=1000, dur=totalTime).play() self.pitch = Pow(1.0594630943593, self.pitch_in, self.user_pitch) if CeciliaLib.getVar("automaticMidiBinding") and CeciliaLib.getVar("useMidi"): self.checkNoteIn = Notein(poly=1, scale=0, first=0, last=120) self.onNewNote = Change(self.checkNoteIn["pitch"]) self.noteTrigFunc = TrigFunc(self.onNewNote, self.newMidiPitch) if self.mode == 0: self.table = SndTable(info['path'], start=info["off" + self.name]) elif self.mode == 2: self.table = NewTable(length=self.sampler.getOffset(), chnls=chnls) self.livein = Input(chnl=[x for x in range(chnls)], mul=0.7) self.filltabrec = TableRec(self.livein, self.table).play() elif self.mode == 3: self.table = NewTable(length=self.sampler.getOffset(), chnls=chnls) self.table2 = NewTable(length=self.sampler.getOffset(), chnls=chnls) self.livein = Input(chnl=[x for x in range(chnls)], mul=0.7) self.rectrig = Metro(time=self.sampler.getOffset(), poly=2).play() self.tmprec1 = TrigTableRec(self.livein, self.rectrig[0], self.table) self.tmprec2 = TrigTableRec(self.livein, self.rectrig[1], self.table2) self.morphind = Counter(self.rectrig.mix(1), min=0, max=2, dir=0) self.interp = SigTo(self.morphind, time=0.1) self.pitch_rnd = [x for x in self.parent.polyphony_spread for y in range(len(self.table))] if self.mode != 3: self.looper = Looper(table=self.table, pitch=self.pitch * self.pitch_rnd, start=self.start, dur=self.dur, xfade=self.xfade, mode=sinfo['loopMode'], xfadeshape=sinfo['xfadeshape'], startfromloop=sinfo['startFromLoop'], interp=4, autosmooth=True, mul=self.gain) self.mix = Mix(self.looper, voices=chnls, mul=self.parent.polyphony_scaling) else: self.looper = Looper(table=self.table, pitch=self.pitch * self.pitch_rnd, start=self.start, dur=self.dur, xfade=self.xfade, mode=sinfo['loopMode'], xfadeshape=sinfo['xfadeshape'], startfromloop=sinfo['startFromLoop'], interp=4, autosmooth=True, mul=self.gain) self.looper2 = Looper(table=self.table2, pitch=self.pitch * self.pitch_rnd, start=self.start, dur=self.dur, xfade=self.xfade, mode=sinfo['loopMode'], xfadeshape=sinfo['xfadeshape'], startfromloop=sinfo['startFromLoop'], interp=4, autosmooth=True, mul=self.gain) self.loopinterp = Interp(self.looper, self.looper2, self.interp) self.mix = Mix(self.loopinterp, voices=chnls, mul=self.parent.polyphony_scaling) else: self.mix = Input(chnl=[x for x in range(chnls)], mul=0.7) def setValueFromOSC(self, val, name): if self.mode != 1: if name == 'start': val = rescale(val, ymin=self.start_mini, ymax=self.start_maxi) self.sampler.getSamplerFrame().loopInSlider.setValue(val) if not self.sampler.getSamplerFrame().loopInSlider.slider.IsShownOnScreen(): self.sampler.getSamplerFrame().loopInSlider.sendValue(val) elif name == 'dur': val = rescale(val, ymin=self.dur_mini, ymax=self.dur_maxi) self.sampler.getSamplerFrame().loopOutSlider.setValue(val) if not self.sampler.getSamplerFrame().loopOutSlider.slider.IsShownOnScreen(): self.sampler.getSamplerFrame().loopOutSlider.sendValue(val) elif name == 'xfade': val = rescale(val, ymin=0, ymax=50) self.sampler.getSamplerFrame().loopXSlider.setValue(val) if not self.sampler.getSamplerFrame().loopXSlider.slider.IsShownOnScreen(): self.sampler.getSamplerFrame().loopXSlider.sendValue(val) elif name == 'gain': val = rescale(val, ymin=-48, ymax=18) self.sampler.getSamplerFrame().gainSlider.setValue(val) if not self.sampler.getSamplerFrame().gainSlider.slider.IsShownOnScreen(): self.sampler.getSamplerFrame().gainSlider.sendValue(val) elif name == 'pitch': val = rescale(val, ymin=-48, ymax=48) self.sampler.getSamplerFrame().transpSlider.setValue(val) if not self.sampler.getSamplerFrame().transpSlider.slider.IsShownOnScreen(): self.sampler.getSamplerFrame().transpSlider.sendValue(val) def getWidget(self, name): if name == 'start': widget = self.sampler.getSamplerFrame().loopInSlider elif name == 'dur': widget = self.sampler.getSamplerFrame().loopOutSlider elif name == 'xfade': widget = self.sampler.getSamplerFrame().loopXSlider elif name == 'gain': widget = self.sampler.getSamplerFrame().gainSlider elif name == 'pitch': widget = self.sampler.getSamplerFrame().transpSlider return widget def updateWidgets(self): if self.mode != 1: if self.start_midi and not self.start_play: val = self.start.get() wx.CallAfter(self.sampler.getSamplerFrame().loopInSlider.setValue, val) if self.dur_midi and not self.dur_play: val = self.dur.get() wx.CallAfter(self.sampler.getSamplerFrame().loopOutSlider.setValue, val) if self.xfade_midi and not self.xfade_play: val = self.xfade.get() wx.CallAfter(self.sampler.getSamplerFrame().loopXSlider.setValue, val) if self.gain_midi and not self.gain_play: val = self.gain_in.get() wx.CallAfter(self.sampler.getSamplerFrame().gainSlider.setValue, val) if self.pitch_midi and not self.pitch_play: val = self.pitch_in.get() wx.CallAfter(self.sampler.getSamplerFrame().transpSlider.setValue, val) def newMidiPitch(self): if not self.pitch_midi: pit = self.checkNoteIn.get() self.sampler.getSamplerFrame().transpSlider.setValue(pit - 60) if not self.sampler.getSamplerFrame().transpSlider.slider.IsShownOnScreen(): self.sampler.getSamplerFrame().transpSlider.sendValue(pit - 60) def setGraph(self, which, func): if self.mode != 1: totalTime = CeciliaLib.getVar("totalTime") func = [(int(x / totalTime * 8192), y) for x, y in func] if which.endswith('start'): self.start_table.replace(func) elif which.endswith('end'): self.dur_table.replace(func) elif which.endswith('xfade'): self.xfade_table.replace(func) elif which.endswith('gain'): self.gain_table.replace(func) elif which.endswith('trans'): self.pitch_table.replace(func) def checkForAutomation(self): if self.mode != 1: if self.start_rec: self.start_record.write() if self.dur_rec: self.dur_record.write() if self.xfade_rec: self.xfade_record.write() if self.gain_rec: self.gain_record.write() if self.pitch_rec: self.pitch_record.write() def sig(self): return self.mix def getDur(self): if self.mode != 1: return self.table.getDur(False) else: return CeciliaLib.getVar("totalTime") def setSound(self, snd): if self.mode == 0: self.table.setSound(snd) def setStart(self, x): if self.mode != 1: if not self.start_play: self.start.value = x def setDur(self, x): if self.mode != 1: if not self.dur_play: self.dur.value = x def setXfade(self, x): if self.mode != 1: if not self.xfade_play: self.xfade.value = x def setGain(self, x): if self.mode != 1: if not self.gain_play: self.gain_in.value = x def setPitch(self, x): if self.mode != 1: if not self.pitch_play: self.pitch_in.value = x def setLoopMode(self, x): if self.mode != 1: self.looper.mode = x if self.mode == 3: self.looper2.mode = x def setXfadeShape(self, x): if self.mode != 1: self.looper.xfadeshape = x if self.mode == 3: self.looper2.xfadeshape = x class CeciliaSlider: def __init__(self, dic, baseModule): self.type = "slider" self.name = dic["name"] gliss = dic["gliss"] up = dic["up"] totalTime = CeciliaLib.getVar("totalTime") self.baseModule = baseModule if not up: for line in CeciliaLib.getVar("grapher").plotter.getData(): if line.name == self.name: break self.widget = line.slider self.play = self.widget.getPlay() self.rec = self.widget.getRec() self.midi = self.widget.getWithMidi() self.openSndCtrl = self.widget.getWithOSC() curved = line.getCurved() log = self.widget.getLog() if curved and log: self.table = CosLogTable() elif curved: self.table = CosTable() elif log: self.table = LogTable() else: self.table = LinTable() else: self.play = self.rec = self.midi = self.openSndCtrl = 0 for slider in CeciliaLib.getVar("userSliders"): if slider.name == self.name: self.widget = slider break init = self.widget.getValue() mini = self.widget.getMinValue() maxi = self.widget.getMaxValue() log = self.widget.getLog() self.slider = SigTo(init, time=gliss, init=init) if self.rec: self.record = ControlRec(self.slider, filename=self.widget.getPath(), rate=1000, dur=totalTime).play() if self.play > 0: data = line.getData() data = [tuple(x) for x in data] self.setGraph(data) self.reader = TableRead(self.table, freq=1.0 / totalTime).play() elif self.midi: if log: init = math.sqrt((init - mini) / (maxi - mini)) * (maxi - mini) + mini exp = 2 else: exp = 1 self.ctlin = Midictl(self.widget.getMidiCtl(), mini, maxi, init, self.widget.getMidiChannel()) self.reader = Scale(SigTo(self.ctlin), inmin=mini, inmax=maxi, outmin=mini, outmax=maxi, exp=exp) elif self.openSndCtrl: port, address = self.widget.getOpenSndCtrl() self.baseModule._addOpenSndCtrlWidget(port, address, self) def sig(self): if self.play == 0: return self.slider else: return self.reader def setValue(self, x): self.slider.value = x def setGraph(self, func): totalTime = CeciliaLib.getVar("totalTime") func2 = [(int(x / totalTime * 8192), y) for x, y in func[:]] oldX = -1 for i, f in enumerate(func2): if f[0] == oldX: func2[i] = (f[0]+1, f[1]) oldX = func2[i][0] self.table.replace(func2) def updateWidget(self): val = self.reader.get() wx.CallAfter(self.widget.setValue, val) def setValueFromOSC(self, val): val = rescale(val, ymin=self.widget.getMinValue(), ymax=self.widget.getMaxValue(), ylog=self.widget.getLog()) wx.CallAfter(self.widget.setValue, val) class CeciliaRange: def __init__(self, dic, baseModule): self.type = "range" self.name = dic["name"] gliss = dic["gliss"] up = dic["up"] totalTime = CeciliaLib.getVar("totalTime") self.baseModule = baseModule self.oscTmpVals = [0, 0] if not up: self.graph_lines = [None, None] for line in CeciliaLib.getVar("grapher").plotter.getData(): if self.name == line.name: if line.suffix == "min": self.graph_lines[0] = line elif line.suffix == "max": self.graph_lines[1] = line self.widget = self.graph_lines[0].slider self.play = self.widget.getPlay() self.rec = self.widget.getRec() self.midi = self.widget.getWithMidi() self.openSndCtrl = self.widget.getWithOSC() curved = [line.getCurved() for line in self.graph_lines] if curved[0]: self.table_min = CosTable() else: self.table_min = LinTable() if curved[1]: self.table_max = CosTable() else: self.table_max = LinTable() else: self.play = self.rec = self.midi = self.openSndCtrl = 0 for slider in CeciliaLib.getVar("userSliders"): if slider.name == self.name: self.widget = slider break init = self.widget.getValue() mini = self.widget.getMinValue() maxi = self.widget.getMaxValue() log = self.widget.getLog() self.slider = SigTo(init, time=gliss, init=init) if self.rec: self.record = ControlRec(self.slider, filename=self.widget.getPath(), rate=1000, dur=totalTime).play() if self.play > 0: data = self.graph_lines[0].getData() data = [tuple(x) for x in data] self.setGraph(0, data) self.reader_min = TableRead(self.table_min, freq=1.0 / totalTime).play() data = self.graph_lines[1].getData() data = [tuple(x) for x in data] self.setGraph(1, data) self.reader_max = TableRead(self.table_max, freq=1.0 / totalTime).play() self.reader = Mix([self.reader_min, self.reader_max], voices=2) elif self.midi: if log: init = [math.sqrt((x - mini) / (maxi - mini)) * (maxi - mini) + mini for x in init] exp = 2 else: exp = 1 self.ctlin = Midictl(self.widget.getMidiCtl(), mini, maxi, init, self.widget.getMidiChannel()) self.reader = Scale(SigTo(self.ctlin), inmin=mini, inmax=maxi, outmin=mini, outmax=maxi, exp=exp) elif self.openSndCtrl: oscTuples = self.widget.getOpenSndCtrl() if oscTuples[0] != (): port, address = oscTuples[0][0], oscTuples[0][1] self.baseModule._addOpenSndCtrlWidget(port, address, self, 0) if oscTuples[1] != (): port, address = oscTuples[1][0], oscTuples[1][1] self.baseModule._addOpenSndCtrlWidget(port, address, self, 1) def sig(self): if self.play == 0: return self.slider else: return self.reader def setValue(self, x): self.slider.value = x def setGraph(self, which, func): totalTime = CeciliaLib.getVar("totalTime") func = [(int(x / totalTime * 8192), y) for x, y in func] oldX = -1 for i, f in enumerate(func): if f[0] == oldX: func[i] = (f[0]+1, f[1]) oldX = func[i][0] if which == 0: self.table_min.replace(func) else: self.table_max.replace(func) def updateWidget(self): val = self.reader.get(all=True) wx.CallAfter(self.widget.setValue, val) def setValueFromOSC(self, val, which): val = rescale(val, ymin=self.widget.getMinValue(), ymax=self.widget.getMaxValue(), ylog=self.widget.getLog()) wx.CallAfter(self.widget.setOneValue, val, which) class CeciliaSplitter: def __init__(self, dic): self.type = "splitter" self.name = dic["name"] gliss = dic["gliss"] up = dic["up"] self.num_knobs = dic["num_knobs"] totalTime = CeciliaLib.getVar("totalTime") if 0: #if not up: self.graph_lines = [None for i in range(self.num_knobs)] for line in CeciliaLib.getVar("grapher").plotter.getData(): if self.name in line.name: which = int(line.suffix[1:]) self.graph_lines[which] = line self.widget = self.graph_lines[0].slider self.play = self.widget.getPlay() self.rec = self.widget.getRec() self.tables = [] for curved in [line.getCurved() for line in self.graph_lines]: if curved: self.tables.append(CosTable()) else: self.tables.append(LinTable()) else: self.play = self.rec = self.midi = 0 for slider in CeciliaLib.getVar("userSliders"): if slider.name == self.name: self.widget = slider break init = self.widget.getValue() self.slider = SigTo(init, time=gliss, init=init) if self.rec: self.record = ControlRec(self.slider, filename=self.widget.getPath(), rate=1000, dur=totalTime).play() if self.play > 0: self.readers = [] for i in range(self.num_knobs): data = self.graph_lines[i].getData() data = [tuple(x) for x in data] self.setGraph(i, data) self.readers.append(TableRead(self.tables[i], freq=1.0 / totalTime).play()) self.reader = Mix(self.readers, voices=self.num_knobs) def sig(self): if self.play == 0: return self.slider else: return self.reader def setValue(self, x): self.slider.value = x def setGraph(self, which, func): totalTime = CeciliaLib.getVar("totalTime") func = [(int(x / totalTime * 8192), y) for x, y in func] oldX = -1 for i, f in enumerate(func): if f[0] == oldX: func[i] = (f[0]+1, f[1]) oldX = func[i][0] self.tables[which].replace(func) def updateWidget(self): val = self.reader.get(all=True) wx.CallAfter(self.widget.setValue, val) class CeciliaGraph: def __init__(self, dic): self.name = dic["name"] self.isTable = dic["table"] self.size = dic["size"] totalTime = CeciliaLib.getVar("totalTime") for line in CeciliaLib.getVar("grapher").plotter.getData(): if line.name == self.name: break func = [(int(x / totalTime * (self.size - 1)), y) for x, y in line.getData()] oldX = -1 for i, f in enumerate(func): if f[0] == oldX: func[i] = (f[0]+1, f[1]) oldX = func[i][0] curved = line.getCurved() if curved: self.table = CosTable(func, size=self.size) else: self.table = LinTable(func, size=self.size) if not self.isTable: self.reader = TableRead(self.table, freq=1.0 / totalTime).play() def sig(self): if self.isTable: return self.table else: return self.reader def setValue(self, func): totalTime = CeciliaLib.getVar("totalTime") func = [(int(x / totalTime * (self.size - 1)), y) for x, y in func] oldX = -1 for i, f in enumerate(func): if f[0] == oldX: func[i] = (f[0]+1, f[1]) oldX = func[i][0] self.table.replace(func) class BaseModule: def __init__(self): self._fileins = {} self._samplers = {} self._sliders = {} self._toggles = {} self._popups = {} self._buttons = {} self._gens = {} self._graphs = {} self._polyphony = None self._openSndCtrlDict = {} self._openSndCtrlSliderDict = {} self._OSCOutList = [] ###### Public attributes ###### self.sr = CeciliaLib.getVar("sr") self.nchnls = CeciliaLib.getVar("nchnls") self.totalTime = CeciliaLib.getVar("totalTime") self.server = CeciliaLib.getVar("audioServer").server self.filepath = os.path.split(CeciliaLib.getVar("currentCeciliaFile", unicode=True))[0] self.number_of_voices = 1 self.polyphony_spread = [1.0] self.polyphony_scaling = 1.0 ############################### interfaceWidgets = CeciliaLib.getVar("interfaceWidgets") for widget in interfaceWidgets: if widget['type'] in ["cslider", "crange", "csplitter"]: self._addSlider(widget, widget["type"]) elif widget['type'] == "cgraph": self._addGraph(widget) elif widget['type'] == "ctoggle": if widget['rate'] == "k": self._toggles[widget["name"]] = widget elif widget['type'] == "cpopup": if widget['rate'] == "k": self._popups[widget["name"]] = widget elif widget['type'] == "cbutton": self._buttons[widget["name"]] = widget elif widget['type'] == "cgen": if widget['rate'] == "k": self._gens[widget["name"]] = widget elif widget['type'] == "cpoly" and self._polyphony is None: self._polyphony = widget userTogglePopups = CeciliaLib.getVar("userTogglePopups") polyname = "noPolyphonyWidget" if self._polyphony is not None: polyname = self._polyphony["name"] for togPop in userTogglePopups: if togPop.name.startswith(polyname): if togPop.name.endswith("num"): self.number_of_voices = togPop.getValue() + 1 else: self._definePolyTranspo(togPop.getLabel()) if togPop.type == "popup": name = togPop.name setattr(self, name + "_index", togPop.getValue()) setattr(self, name + "_value", togPop.getLabel()) elif togPop.type == "toggle": name = togPop.name setattr(self, name + "_value", togPop.getValue()) elif togPop.type == "gen": name = togPop.name setattr(self, name + "_value", togPop.getValue()) self._metro = Metro(.06).play(dur=self.totalTime) self._updater = TrigFunc(self._metro, self._updateWidgets).play(dur=self.totalTime) ###### Public methods ###### def addFilein(self, name): """ Creates a SndTable object from the name of a cfilein widget. A SndTable is a memory filled with the samples of the current audio file. Parameters: name : string The name assigned to a cfilein widget in the Interface declaration. """ self._fileins[name] = CeciliaFilein(self, name) return self._fileins[name].sig() def addSampler(self, name, pitch=1, amp=1): """ Creates a sampler/looper from the name of a csampler widget. A sampler comes with an interface window allowing the control of the looping parameters (direction, start point, loop duration, crossfade duration, gain, pitch, etc.) Parameters: pitch : float or PyoObject External pitch control added to the value of the transposition slider. amp : float or PyoObject External amplitude control added to the value of the gain slider. """ self._samplers[name] = CeciliaSampler(self, name, pitch, amp) return self._samplers[name].sig() def getSamplerDur(self, name): return self._samplers[name].getDur() def duplicate(self, seq, num): """ Duplicates elements in a sequence according to the `num` parameter. This method is useful to creates lists that match the number of channels multiplied by the number of voices from a cpoly or a splitter widget. Parameters: seq : list List of values to duplicate. num : int Number of iteration for each value in the sequence. Example: freqs = duplicate([100, 200, 300], 4) print(freqs) [100, 100, 100, 100, 200, 200, 200, 200, 300, 300, 300, 300] """ tmp = [x for x in seq for i in range(num)] return tmp def setGlobalSeed(self, x): """ Sets the Server's global seed used by objects from the random family. """ CeciliaLib.getVar("audioServer").server.globalseed = x ############################ ###### Private methods ###### def _definePolyTranspo(self, chord): if self.number_of_voices <= 1: return tmp = 0 for i in range(self.number_of_voices): tmp -= {0: 0, 1: 3, 2: 3, 3: 2, 4: 2, 5: 2, 6: 1, 7: 1, 8: 1, 9: 1}.get(i, 1) self.polyphony_scaling = pow(10.0, tmp * 0.05) if chord == "None": self.polyphony_spread = [1.0] * self.number_of_voices return else: self.polyphony_spread = [1.0] pool = POLY_CHORDS[chord] for i in range(1, self.number_of_voices): note = pool[i % len(pool)] if i % 2 == 1 or note < 1.0: self.polyphony_spread.append(midiToTranspo(note + 60)) else: self.polyphony_spread.append(midiToTranspo(note - 12 + 60)) def _deleteOscReceivers(self): if hasattr(self, "oscReceivers"): for key in list(self.oscReceivers.keys()): del self.oscReceivers[key] del self.oscReceivers def _createOpenSndCtrlReceivers(self): if self._openSndCtrlDict: self.oscReceivers = {} for key in self._openSndCtrlDict.keys(): self.oscReceivers[key] = OscReceive(key, self._openSndCtrlDict[key]) for slider in self._openSndCtrlSliderDict[key]: if isinstance(slider, tuple): slider, side = slider[0], slider[1] if slider.type == "sampler": # sampler slider widget = slider.getWidget(side) path = widget.openSndCtrl[1] val = rescale(widget.getValue(), xmin=widget.getMinValue(), xmax=widget.getMaxValue(), xlog=widget.getLog()) self.oscReceivers[key].setValue(path, val) if widget.OSCOut is not None: tmpout = OscDataSend("f", widget.OSCOut[1], widget.OSCOut[2], widget.OSCOut[0]) tmpout.send([val]) self._OSCOutList.append(tmpout) else: # range slider widget = slider.widget path = widget.openSndCtrl[side][1] val = rescale(widget.getValue()[side], xmin=widget.getMinValue(), xmax=widget.getMaxValue(), xlog=widget.getLog()) self.oscReceivers[key].setValue(path, val) if widget.OSCOut is not None: if widget.OSCOut[side] != (): tmpout = OscDataSend("f", widget.OSCOut[side][1], widget.OSCOut[side][2], widget.OSCOut[side][0]) tmpout.send([val]) self._OSCOutList.append(tmpout) else: # slider TODO: could be merged with sampler slider code. widget = slider.widget path = widget.openSndCtrl[1] val = rescale(widget.getValue(), xmin=widget.getMinValue(), xmax=widget.getMaxValue(), xlog=widget.getLog()) self.oscReceivers[key].setValue(path, val) if widget.OSCOut is not None: tmpout = OscDataSend("f", widget.OSCOut[1], widget.OSCOut[2], widget.OSCOut[0]) tmpout.send([val]) self._OSCOutList.append(tmpout) def _addOpenSndCtrlWidget(self, port, address, slider, side=0, name=""): if port in self._openSndCtrlDict: self._openSndCtrlDict[port].append(address) if slider.type == 'sampler': self._openSndCtrlSliderDict[port].append((slider, name)) elif slider.type == 'slider': self._openSndCtrlSliderDict[port].append(slider) elif slider.type == 'range': self._openSndCtrlSliderDict[port].append((slider, side)) else: self._openSndCtrlDict[port] = [address] if slider.type == 'sampler': self._openSndCtrlSliderDict[port] = [(slider, name)] elif slider.type == 'slider': self._openSndCtrlSliderDict[port] = [slider] elif slider.type == 'range': self._openSndCtrlSliderDict[port] = [(slider, side)] def _checkForAutomation(self): for sampler in self._samplers.values(): sampler.checkForAutomation() for slider in self._sliders.values(): if slider.rec: slider.record.write() def _updateWidgets(self): if self._samplers != {}: for key in self._samplers.keys(): self._samplers[key].updateWidgets() for slider in self._sliders.values(): if slider.play == 1 or slider.midi: slider.updateWidget() if self._openSndCtrlDict: for key in self._openSndCtrlDict.keys(): values = self.oscReceivers[key].get(all=True) for i in range(len(values)): slider = self._openSndCtrlSliderDict[key][i] if isinstance(slider, tuple): which = slider[1] slider[0].setValueFromOSC(values[i], which) else: slider.setValueFromOSC(values[i]) CeciliaLib.getVar("audioServer").updatePluginWidgets() def _setWidgetValues(self): # graph lines for line in CeciliaLib.getVar("grapher").plotter.getData(): name = line.getName() if name in self._graphs.keys(): data = line.getData() self._graphs[name].setValue(data) # sliders for slider in CeciliaLib.getVar("userSliders"): name = slider.getName() self._sliders[name].setValue(slider.getValue()) # toggles and popups for togPop in CeciliaLib.getVar("userTogglePopups"): name = togPop.getName() if name in self._toggles: getattr(self, name)(togPop.getValue()) elif name in self._popups: index, label = togPop.getFullValue() getattr(self, name)(index, label) elif name in self._gens: getattr(self, name)(togPop.getValue()) def _addSlider(self, dic, typ): if typ == "cslider": self._sliders[dic["name"]] = CeciliaSlider(dic, self) elif typ == "crange": self._sliders[dic["name"]] = CeciliaRange(dic, self) elif typ == "csplitter": self._sliders[dic["name"]] = CeciliaSplitter(dic) setattr(self, dic["name"], self._sliders[dic["name"]].sig()) def _addGraph(self, dic): self._graphs[dic["name"]] = CeciliaGraph(dic) setattr(self, dic["name"], self._graphs[dic["name"]].sig()) def _cleanup(self): self.oscReceivers = {} self._OSCOutList = [] for key in list(self.__dict__.keys()): del self.__dict__[key] class CeciliaPlugin: def __init__(self, input, params=None, knobs=None): self.input = Sig(input) gliss = 0.05 totalTime = CeciliaLib.getVar("totalTime") if params is None: self._p1 = SigTo(0, time=0.025) self._p2 = SigTo(0, time=0.025) self._p3 = SigTo(0, time=0.025) else: # TODO: merge these three chunks in a single loop. self.widget_p1 = knobs[0] name = self.widget_p1.getName() for line in CeciliaLib.getVar("grapher").plotter.getData(): if line.name == name: break self.play_p1 = self.widget_p1.getPlay() self.rec_p1 = self.widget_p1.getRec() self.midi_p1 = self.widget_p1.getWithMidi() curved = line.getCurved() if curved: self.table_p1 = CosTable() else: self.table_p1 = LinTable() init = self.widget_p1.getValue() mini = self.widget_p1.getMinValue() maxi = self.widget_p1.getMaxValue() log = self.widget_p1.getLog() self._p1 = SigTo(params[0], time=gliss, init=params[0]) if self.rec_p1: self.record_p1 = ControlRec(self._p1, filename=self.widget_p1.getPath(), rate=1000, dur=totalTime).play() if self.play_p1 > 0: data = line.getData() data = [tuple(x) for x in data] self.setGraph(0, data) self.reader_p1 = TableRead(self.table_p1, freq=1.0 / totalTime).play() elif self.midi_p1: if log: init = math.sqrt((init - mini) / (maxi - mini)) * (maxi - mini) + mini exp = 2 else: exp = 1 self.ctlin_p1 = Midictl(self.widget_p1.getMidiCtl(), mini, maxi, init, self.widget_p1.getMidiChannel()) self.reader_p1 = Scale(self.ctlin_p1, inmin=mini, inmax=maxi, outmin=mini, outmax=maxi, exp=exp) self.widget_p2 = knobs[1] name = self.widget_p2.getName() for line in CeciliaLib.getVar("grapher").plotter.getData(): if line.name == name: break self.play_p2 = self.widget_p2.getPlay() self.rec_p2 = self.widget_p2.getRec() self.midi_p2 = self.widget_p2.getWithMidi() curved = line.getCurved() if curved: self.table_p2 = CosTable() else: self.table_p2 = LinTable() init = self.widget_p2.getValue() mini = self.widget_p2.getMinValue() maxi = self.widget_p2.getMaxValue() log = self.widget_p2.getLog() self._p2 = SigTo(params[1], time=gliss, init=params[1]) if self.rec_p2: self.record_p2 = ControlRec(self._p2, filename=self.widget_p2.getPath(), rate=1000, dur=totalTime).play() if self.play_p2 > 0: data = line.getData() data = [tuple(x) for x in data] self.setGraph(1, data) self.reader_p2 = TableRead(self.table_p2, freq=1.0 / totalTime).play() elif self.midi_p2: if log: init = math.sqrt((init - mini) / (maxi - mini)) * (maxi - mini) + mini exp = 2 else: exp = 1 self.ctlin_p2 = Midictl(self.widget_p2.getMidiCtl(), mini, maxi, init, self.widget_p2.getMidiChannel()) self.reader_p2 = Scale(self.ctlin_p2, inmin=mini, inmax=maxi, outmin=mini, outmax=maxi, exp=exp) self.widget_p3 = knobs[2] name = self.widget_p3.getName() for line in CeciliaLib.getVar("grapher").plotter.getData(): if line.name == name: break self.play_p3 = self.widget_p3.getPlay() self.rec_p3 = self.widget_p3.getRec() self.midi_p3 = self.widget_p3.getWithMidi() curved = line.getCurved() if curved: self.table_p3 = CosTable() else: self.table_p3 = LinTable() init = self.widget_p3.getValue() mini = self.widget_p3.getMinValue() maxi = self.widget_p3.getMaxValue() log = self.widget_p3.getLog() self._p3 = SigTo(params[2], time=gliss, init=params[2]) if self.rec_p3: self.record_p3 = ControlRec(self._p3, filename=self.widget_p3.getPath(), rate=1000, dur=totalTime).play() if self.play_p3 > 0: data = line.getData() data = [tuple(x) for x in data] self.setGraph(2, data) self.reader_p3 = TableRead(self.table_p3, freq=1.0 / totalTime).play() elif self.midi_p3: if log: init = math.sqrt((init - mini) / (maxi - mini)) * (maxi - mini) + mini exp = 2 else: exp = 1 self.ctlin_p3 = Midictl(self.widget_p3.getMidiCtl(), mini, maxi, init, self.widget_p3.getMidiChannel()) self.reader_p3 = Scale(self.ctlin_p3, inmin=mini, inmax=maxi, outmin=mini, outmax=maxi, exp=exp) self.preset = params[3] def sig1(self): if self.play_p1 == 0 and not self.midi_p1: return self._p1 else: return self.reader_p1 def sig2(self): if self.play_p2 == 0 and not self.midi_p2: return self._p2 else: return self.reader_p2 def sig3(self): if self.play_p3 == 0 and not self.midi_p3: return self._p3 else: return self.reader_p3 def setPreset(self, x, label): self.preset = x if self.preset == 0: self.out.interp = 0 else: self.out.interp = 1 def setInput(self, input): self.input.value = input def setGraph(self, which, func): totalTime = CeciliaLib.getVar("totalTime") func = [(int(x / totalTime * 8192), y) for x, y in func] if which == 0: self.table_p1.replace(func) elif which == 1: self.table_p2.replace(func) elif which == 2: self.table_p3.replace(func) def stopControls(self): self._p1.stop() self._p2.stop() self._p3.stop() def startControls(self): self._p1.play() self._p2.play() self._p3.play() def setValue(self, which, x): if which == 0: self._p1.value = x elif which == 1: self._p2.value = x elif which == 2: self._p3.value = x def checkForAutomation(self): if self.rec_p1: self.record_p1.write() if self.rec_p2: self.record_p2.write() if self.rec_p3: self.record_p3.write() def updateWidget(self): if self.play_p1 or self.midi_p1: val = self.reader_p1.get() wx.CallAfter(self.widget_p1.setValue, val) if self.play_p2 or self.midi_p2: val = self.reader_p2.get() wx.CallAfter(self.widget_p2.setValue, val) if self.play_p3 or self.midi_p3: val = self.reader_p3.get() wx.CallAfter(self.widget_p3.setValue, val) class CeciliaNonePlugin(CeciliaPlugin): def __init__(self, input): CeciliaPlugin.__init__(self, input) self.out = self.input self.stopControls() class CeciliaReverbPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) self.out = Freeverb(self.input, size=self.sig2() * 0.1, damp=self.sig3(), bal=self.sig1() * self.preset) def setPreset(self, x, label): self.preset = x self.out.bal = self.sig1() * self.preset class CeciliaWGReverbPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) self.out = WGVerb(self.input, feedback=self.sig2(), cutoff=self.sig3(), bal=self.sig1() * self.preset) def setPreset(self, x, label): self.preset = x self.out.bal = self.sig1() * self.preset class CeciliaFilterPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 typ = 0 else: inter = 1 typ = self.preset - 1 self.filter = Biquad(self.input, freq=self.sig2(), q=self.sig3(), type=typ, mul=self.sig1()) self.out = Interp(self.input, self.filter, inter) def setPreset(self, x, label): self.preset = x if self.preset == 0: self.out.interp = 0 else: self.filter.type = self.preset - 1 self.out.interp = 1 class CeciliaEQPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 typ = 0 else: inter = 1 typ = self.preset - 1 self.filter = EQ(self.input, freq=self.sig1(), q=self.sig2(), boost=self.sig3(), type=typ) self.out = Interp(self.input, self.filter, inter) def setPreset(self, x, label): self.preset = x if self.preset == 0: self.out.interp = 0 else: self.filter.type = self.preset - 1 self.out.interp = 1 class CeciliaChorusPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) self.out = Chorus(self.input, depth=self.sig2(), feedback=self.sig3(), bal=self.sig1() * self.preset) def setPreset(self, x, label): self.preset = x self.out.bal = self.sig1() * self.preset class CeciliaEQ3BPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.low = EQ(self.input, freq=250, q=0.707, boost=self.sig1(), type=1) self.mid = EQ(self.low, freq=1500, q=0.707, boost=self.sig2(), type=0) self.high = EQ(self.mid, freq=2500, q=0.707, boost=self.sig3(), type=2) self.out = Interp(self.input, self.high, inter) class CeciliaCompressPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.dbtoamp = DBToA(self.sig3()) self.comp = Compress(self.input, thresh=self.sig1(), ratio=self.sig2(), lookahead=4, knee=.5, mul=self.dbtoamp) self.out = Interp(self.input, self.comp, inter) class CeciliaGatePlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.gate = Gate(self.input, thresh=self.sig1(), risetime=self.sig2(), falltime=self.sig3(), lookahead=4) self.out = Interp(self.input, self.gate, inter) class CeciliaDistoPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.gain = DBToA(self.sig3()) self.disto = Disto(self.input, drive=self.sig1(), slope=self.sig2(), mul=self.gain) self.out = Interp(self.input, self.disto, inter) class CeciliaAmpModPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: mode = self.preset - 1 inter = 1 self.zero = Sig(0) if len(self.input) < 2: self.lfoamp = Sine(freq=self.sig1(), mul=.5, add=.5) self.iamp = 1.0 - self.sig2() self.modamp = self.input * (self.lfoamp * self.sig2() + self.iamp) self.lforing = Sine(freq=self.sig1(), mul=self.sig2()) self.modring = self.input * self.lforing else: self.lfoamp = Sine(freq=self.sig1(), phase=[self.zero, self.sig3()], mul=.5, add=.5) self.iamp = 1.0 - self.sig2() self.modamp = self.input * (self.lfoamp * self.sig2() + self.iamp) self.lforing = Sine(freq=self.sig1(), phase=[self.zero, self.sig3()], mul=self.sig2()) self.modring = self.input * self.lforing if mode == 0: self.out = Interp(self.input, self.modamp, inter) else: self.out = Interp(self.input, self.modring, inter) def setPreset(self, x, label): self.preset = x if self.preset == 0: self.out.interp = 0 else: if self.preset == 1: self.out.input2 = self.modamp else: self.out.input2 = self.modring self.out.interp = 1 class CeciliaPhaserPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.phaser = Phaser(self.input, freq=self.sig1(), spread=self.sig3(), q=self.sig2(), feedback=0.8, num=8, mul=.5) self.out = Interp(self.input, self.phaser, inter) class CeciliaDelayPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.gain = DBToA(self.sig3()) self.delaytime = SigTo(self.sig1(), time=.1, init=.1) self.delay = Delay(self.input, delay=self.delaytime, feedback=self.sig2()) self.delaymix = Interp(self.input, self.delay, self.sig3()) self.out = Interp(self.input, self.delaymix, inter) class CeciliaFlangePlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.lfo = Sine(freq=self.sig2(), mul=self.sig1() * 0.005, add=0.005) self.delay = Delay(self.input, delay=self.lfo, feedback=self.sig3()) self.delaymix = self.delay + self.input self.out = Interp(self.input, self.delaymix, inter) class CeciliaHarmonizerPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.harmo = Harmonizer(self.input, transpo=self.sig1(), feedback=self.sig2()) self.mix = Interp(self.input, self.harmo, self.sig3()) self.out = Interp(self.input, self.mix, inter) class CeciliaResonatorsPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.wg1 = Waveguide(self.input, freq=self.sig1(), dur=30, mul=.05) self.f2 = self.sig1() * self.sig2() self.wg2 = Waveguide(self.input, freq=self.f2, dur=30, mul=.05) self.f3 = self.sig1() * self.sig2() * self.sig2() self.wg3 = Waveguide(self.input, freq=self.f3, dur=30, mul=.05) self.f4 = self.sig1() * self.sig2() * self.sig2() * self.sig2() self.wg4 = Waveguide(self.input, freq=self.f4, dur=30, mul=.05) self.total = self.wg1 + self.wg2 + self.wg3 + self.wg4 self.mix = Interp(self.input, self.total, self.sig3()) self.out = Interp(self.input, self.mix, inter) class CeciliaDeadResonPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) if self.preset == 0: inter = 0 else: inter = 1 self.wg1 = AllpassWG(self.input, freq=self.sig1(), feed=.995, detune=self.sig2(), mul=.1) self.wg2 = AllpassWG(self.input, freq=self.sig1() * 0.993, feed=.995, detune=self.sig2(), mul=.1) self.total = self.wg1 + self.wg2 self.mix = Interp(self.input, self.total, self.sig3()) self.out = Interp(self.input, self.mix, inter) class CeciliaChaosModPlugin(CeciliaPlugin): def __init__(self, input, params, knobs): CeciliaPlugin.__init__(self, input, params, knobs) self.lfolo = Lorenz(self.sig1(), self.sig2(), stereo=True, mul=0.5, add=0.5).stop() self.lforo = Rossler(self.sig1(), self.sig2(), stereo=True, mul=0.5, add=0.5).stop() self.lfoch = ChenLee(self.sig1(), self.sig2(), stereo=True, mul=0.5, add=0.5).stop() self.lfo = Sig(self.lfolo) self.iamp = 1.0 - self.sig3() self.modu = self.input * (self.lfo * self.sig3() + self.iamp) if self.preset == 0: inter = 0 else: if self.preset == 1: self.lfo.value = self.lfolo.play() elif self.preset == 2: self.lfo.value = self.lfoch.play() else: self.lfo.value = self.lforo.play() inter = 1 self.out = Interp(self.input, self.modu, inter) def setPreset(self, x, label): self.preset = x if self.preset == 0: self.out.interp = 0 else: self.lfo.value.stop() if self.preset == 1: self.lfo.value = self.lfolo.play() elif self.preset == 2: self.lfo.value = self.lfoch.play() else: self.lfo.value = self.lforo.play() self.out.interp = 1 class AudioServer(): def __init__(self): self.amp = 1.0 sr, bufsize, nchnls, duplex, host, outdev, indev, firstin, firstout = self.getPrefs() jackname = CeciliaLib.getVar("jack").get("client", "cecilia5") if CeciliaLib.getVar("DEBUG"): print("AUDIO CONFIG:") template = "sr: %s, buffer size: %s, num of channels: %s, duplex: %s, host: %s, output device: %s, input device: %s" print(template % (sr, bufsize, nchnls, duplex, host, outdev, indev)) print("first physical input: %s, first physical output: %s\n" % (firstin, firstout)) self.server = Server(sr=sr, buffersize=bufsize, nchnls=nchnls, duplex=duplex, audio=host, jackname=jackname) if CeciliaLib.getVar("DEBUG"): self.server.verbosity = 15 if host == 'jack': self.server.setJackAuto(True, True) self.setTimeCallable() self.timeOpened = True self.recording = False self.withTimer = False self.withSpectrum = False self.pluginObjs = [None] * NUM_OF_PLUGINS self.out = self.spectrum = None self.pluginDict = {"Reverb": CeciliaReverbPlugin, "WGVerb": CeciliaWGReverbPlugin, "Filter": CeciliaFilterPlugin, "Para EQ": CeciliaEQPlugin, "Chorus": CeciliaChorusPlugin, "3 Bands EQ": CeciliaEQ3BPlugin, "Compress": CeciliaCompressPlugin, "Gate": CeciliaGatePlugin, "Disto": CeciliaDistoPlugin, "AmpMod": CeciliaAmpModPlugin, "Phaser": CeciliaPhaserPlugin, "Delay": CeciliaDelayPlugin, "Flange": CeciliaFlangePlugin, "Harmonizer": CeciliaHarmonizerPlugin, "Resonators": CeciliaResonatorsPlugin, "DeadReson": CeciliaDeadResonPlugin, 'ChaosMod': CeciliaChaosModPlugin} def updateDebug(self): if CeciliaLib.getVar("DEBUG"): self.server.verbosity = 15 else: self.server.verbosity = 7 def getPrefs(self): sr = CeciliaLib.getVar("sr") bufsize = int(CeciliaLib.getVar("bufferSize")) nchnls = CeciliaLib.getVar("nchnls") duplex = CeciliaLib.getVar("enableAudioInput") host = CeciliaLib.getVar("audioHostAPI") outdev = CeciliaLib.getVar("audioOutput") indev = CeciliaLib.getVar("audioInput") firstin = CeciliaLib.getVar("defaultFirstInput") firstout = CeciliaLib.getVar("defaultFirstOutput") return sr, bufsize, nchnls, duplex, host, outdev, indev, firstin, firstout def dump(self, l): pass def start(self, timer=True, rec=False): if CeciliaLib.getVar("DEBUG"): print("Audio server start: begin") self.timeOpened = True fade = CeciliaLib.getVar("globalFade") self.globalamp = Fader(fadein=fade, fadeout=fade, dur=CeciliaLib.getVar("totalTime")).play() self.out.mul = self.globalamp if CeciliaLib.getVar("automaticMidiBinding") and CeciliaLib.getVar("useMidi"): if CeciliaLib.getVar("DEBUG"): print("Audio server start: use midi") print("midi input device: %d" % CeciliaLib.getVar("midiDeviceIn")) self.checkCtl7 = Midictl(ctlnumber=7, minscale=-48, maxscale=18, init=0) self.onNewCtl7Value = Change(self.checkCtl7) self.ctl7TrigFunc = TrigFunc(self.onNewCtl7Value, self.newCtl7Value) if rec: self.recording = True fileformat = AUDIO_FILE_FORMATS[CeciliaLib.getVar("audioFileType")] sampletype = CeciliaLib.getVar("sampSize") self.recamp = SigTo(self.amp, time=0.05, init=self.amp) self.recorder = Record(self.pluginObjs[-1].out * self.recamp, CeciliaLib.getVar("outputFile"), CeciliaLib.getVar("nchnls"), fileformat=fileformat, sampletype=sampletype, buffering=8) if CeciliaLib.getVar("showSpectrum"): self.withSpectrum = True self.specamp = SigTo(self.amp, time=0.05, init=self.amp, mul=self.pluginObjs[-1].out) self.spectrum = Spectrum(self.specamp, function=self.dump) if CeciliaLib.getVar("startOffset") > 0.0: self.server.startoffset = CeciliaLib.getVar("startOffset") if timer: self.withTimer = True self.server.start() else: self.server.start() CeciliaLib.resetControls() if CeciliaLib.getVar("DEBUG"): print("Audio server start: end\n") def stop(self): if CeciliaLib.getVar("DEBUG"): print("Audio server stop: begin") if self.withTimer: self.withTimer = False self.server.stop() if self.recording: self.recording = False self.recorder.stop() if self.withSpectrum: self.withSpectrum = False self.spectrum.poll(False) self.spectrum.stop() self.timeOpened = False if CeciliaLib.getVar("grapher") is not None: CeciliaLib.getVar("grapher").cursorPanel.setTime(CeciliaLib.getVar("startOffset")) time.sleep(.15) if CeciliaLib.getVar("currentModule") is not None: CeciliaLib.getVar("currentModule")._deleteOscReceivers() if CeciliaLib.getVar("DEBUG"): print("Audio server stop: end\n") def shutdown(self): self.server.shutdown() def boot(self): sr, bufsize, nchnls, duplex, host, outdev, indev, firstin, firstout = self.getPrefs() if CeciliaLib.getVar("DEBUG"): print("AUDIO CONFIG:") template = "sr: %s, buffer size: %s, num of channels: %s, duplex: %s, host: %s, output device: %s, input device: %s" print(template % (sr, bufsize, nchnls, duplex, host, outdev, indev)) print("first physical input: %s, first physical output: %s\n" % (firstin, firstout)) print("MIDI CONFIG: \ninput device: %d\n" % CeciliaLib.getVar("midiDeviceIn")) self.server.setSamplingRate(sr) self.server.setBufferSize(bufsize) self.server.setNchnls(nchnls) self.server.setDuplex(duplex) self.server.setOutputDevice(outdev) self.server.setInputOffset(firstin) self.server.setOutputOffset(firstout) if CeciliaLib.getVar("enableAudioInput"): self.server.setInputDevice(indev) if CeciliaLib.getVar("useMidi"): self.server.setMidiInputDevice(CeciliaLib.getVar("midiDeviceIn")) self.server.boot() def reinit(self): jackname = CeciliaLib.getVar("jack").get("client", "cecilia5") if CeciliaLib.getVar("toDac"): sr, bufsize, nchnls, duplex, host, outdev, indev, firstin, firstout = self.getPrefs() self.server.reinit(audio=host, jackname=jackname) else: self.server.reinit(audio="offline_nb", jackname=jackname) dur = CeciliaLib.getVar("totalTime") filename = CeciliaLib.getVar("outputFile") fileformat = AUDIO_FILE_FORMATS[CeciliaLib.getVar("audioFileType")] sampletype = CeciliaLib.getVar("sampSize") self.server.recordOptions(dur=dur, filename=filename, fileformat=fileformat, sampletype=sampletype) def setAmpCallable(self, callable): self.server._server.setAmpCallable(callable) def setTimeCallable(self): self.server._server.setTimeCallable(self) def setTime(self, *args): if len(args) >= 4 and self.timeOpened: time = args[1] * 60 + args[2] + args[3] * 0.001 CeciliaLib.getVar("grapher").cursorPanel.setTime(time) CeciliaLib.getVar("interface").controlPanel.setTime(time, args[1], args[2], args[3] // 10) if time >= (CeciliaLib.getVar("totalTime") - 0.5): wx.CallAfter(CeciliaLib.getControlPanel().closeBounceToDiskDialog) if time >= (CeciliaLib.getVar("totalTime")): wx.CallAfter(CeciliaLib.stopCeciliaSound) else: CeciliaLib.getVar("grapher").cursorPanel.setTime(CeciliaLib.getVar("startOffset")) CeciliaLib.getVar("interface").controlPanel.setTime(0, 0, 0, 0) def recstart(self): self.server.recstart() def recstop(self): self.server.recstop() def newCtl7Value(self): val = self.checkCtl7.get() CeciliaLib.getControlPanel().gainSlider.SetValue(val) def setAmp(self, x): self.amp = math.pow(10.0, x * 0.05) self.server.amp = self.amp try: self.recamp.value = self.amp except: pass try: self.specamp.value = self.amp except: pass def setInOutDevice(self, device): self.server.setInOutDevice(device) def setMidiInputDevice(self, device): self.server.setMidiInputDevice(device) def setSamplingRate(self, sr): self.server.setSamplingRate(sr) def recordOptions(self, dur, filename, fileformat, sampletype): self.server.recordOptions(dur, filename, fileformat, sampletype) def isAudioServerRunning(self): return self.server.getIsStarted() def isAudioServerBooted(self): return self.server.getIsBooted() def compileRuntimeError(self, filepath, title, msg): error = traceback.format_exc() #print("============== Error:\n", error, "=====================\n") if "exec(f.read(), globals())" in error: pos = error.find("exec(f.read(), globals())") error = error[pos:].replace("exec(f.read(), globals())", "") linenum = -1 if "line " in error: try: pos1 = error.rfind("line ") + 5 pos2 = pos1 while error[pos2] in "0123456789": pos2 += 1 linenum = int(error[pos1:pos2].strip()) except: pass codeline = "" if linenum != -1: try: codeline = "Please review this part of the code and reload the module:\n\n#-----\n" with open(filepath, "r") as f: _text = f.readlines() if linenum > 0: codeline = codeline + _text[linenum-1] + _text[linenum] + "#-----\n" else: codeline = codeline + _text[linenum] + "#-----\n" except: pass tracelines = error tracelines = "Traceback:\n\n" + tracelines.replace("", filepath) + "\n\n" msg = msg + tracelines + codeline CeciliaLib.showErrorDialog(title, msg) def openCecFile(self, filepath): global Module, Interface try: del Module, Interface except: pass if not serverBooted(): self.boot() self.cleanup_module() CeciliaLib.setVar("currentModule", None) CeciliaLib.setVar("currentModuleRef", None) CeciliaLib.setVar("interfaceWidgets", []) CeciliaLib.setVar("startOffset", 0.0) CeciliaLib.checkForPresetsInCeciliaFile(filepath) try: with open(filepath, "r") as f: exec(f.read(), globals()) except Exception as e: # If it fails, show the error and reload the current module. msg = "Cecilia can't compile the chosen module, Current module (or a random one if this failed too) will be reloaded.\n\n" self.compileRuntimeError(filepath, "Syntax Error!", msg) if os.path.isfile(MODULE_COMPILE_BACKUP_PATH): CeciliaLib.openCeciliaFile(None, MODULE_COMPILE_BACKUP_PATH, CeciliaLib.getVar("builtinModule")) else: CeciliaLib.getVar("mainFrame").onOpenRandom(None) return False CeciliaLib.setVar("currentModuleRef", copy.deepcopy(Module)) CeciliaLib.setVar("interfaceWidgets", CeciliaLib.deepCopy(Interface)) CeciliaLib.getVar("mainFrame").onUpdateInterface(None) return True def cleanup_module(self): for i in range(NUM_OF_PLUGINS): if self.pluginObjs[i] is not None: del self.pluginObjs[i].out self.pluginObjs[i] = None if self.spectrum is not None: self.specamp.mul = 1 self.spectrum.setFunction(None) del self.specamp del self.spectrum._timer del self.spectrum self.spectrum = None if hasattr(self, "out"): if self.out is not None: del self.out self.out = None try: del self.globalamp except: pass try: del self.endcall except: pass try: del self.recorder del self.recamp except: pass try: del self.checkCtl7 del self.onNewCtl7Value del self.ctl7TrigFunc except: pass try: CeciliaLib.getVar("currentModule")._cleanup() CeciliaLib.setVar("currentModule", None) except: pass def loadModule(self, module): self.cleanup_module() try: currentModule = module() except Exception as e: # If it fails, show the error and reload the current module. msg = "Cecilia can't run the current module, last valid module (or a random one if this failed too) will be reloaded.\n\n" self.compileRuntimeError(CeciliaLib.getVar("currentCeciliaFile"), "Runtime Error!", msg) if os.path.isfile(MODULE_RUNTIME_BACKUP_PATH): CeciliaLib.openCeciliaFile(None, MODULE_RUNTIME_BACKUP_PATH, CeciliaLib.getVar("builtinModule")) else: CeciliaLib.getVar("mainFrame").onOpenRandom(None) return False currentModule._createOpenSndCtrlReceivers() self.out = Sig(currentModule.out) plugins = CeciliaLib.getVar("plugins") for i in range(NUM_OF_PLUGINS): if i == 0: tmp_out = self.out else: tmp_out = self.pluginObjs[i - 1].out if plugins[i] is None: self.pluginObjs[i] = CeciliaNonePlugin(tmp_out) self.pluginObjs[i].name = "None" else: pl = plugins[i] name, params, knobs = pl.getName(), pl.getParams(), pl.getKnobs() self.pluginObjs[i] = self.pluginDict[name](tmp_out, params, knobs) self.pluginObjs[i].name = name self.pluginObjs[NUM_OF_PLUGINS - 1].out.out() CeciliaLib.setVar("currentModule", currentModule) currentModule._setWidgetValues() CeciliaLib.saveRuntimeBackupFile(CeciliaLib.getVar("currentCeciliaFile")) return True def movePlugin(self, vpos, dir): i1 = vpos i2 = vpos + dir tmp = self.pluginObjs[i2] self.pluginObjs[i2] = self.pluginObjs[i1] self.pluginObjs[i1] = tmp for i in range(NUM_OF_PLUGINS): if i == 0: tmp_out = self.out else: tmp_out = self.pluginObjs[i - 1].out self.pluginObjs[i].setInput(tmp_out) self.pluginObjs[i].out.play() self.pluginObjs[NUM_OF_PLUGINS - 1].out.out() def setPlugin(self, order): plugins = CeciliaLib.getVar("plugins") tmp = self.pluginObjs[order] if order == 0: tmp_out = self.out else: tmp_out = self.pluginObjs[order - 1].out if plugins[order] is None: self.pluginObjs[order] = CeciliaNonePlugin(tmp_out) self.pluginObjs[order].name = "None" else: pl = plugins[order] name, params, knobs = pl.getName(), pl.getParams(), pl.getKnobs() self.pluginObjs[order] = self.pluginDict[name](tmp_out, params, knobs) self.pluginObjs[order].name = name if order < (NUM_OF_PLUGINS - 1): self.pluginObjs[order + 1].setInput(self.pluginObjs[order].out) else: self.pluginObjs[order].out.out() del tmp def checkForAutomation(self): plugins = CeciliaLib.getVar("plugins") for i in range(NUM_OF_PLUGINS): if plugins[i] is not None: if plugins[i].getName() == self.pluginObjs[i].name: self.pluginObjs[i].checkForAutomation() def updatePluginWidgets(self): plugins = CeciliaLib.getVar("plugins") for i in range(NUM_OF_PLUGINS): if plugins[i] is not None: if plugins[i].getName() == self.pluginObjs[i].name: self.pluginObjs[i].updateWidget() def setPluginValue(self, order, which, x): plugins = CeciliaLib.getVar("plugins") if plugins[order] is not None: if plugins[order].getName() == self.pluginObjs[order].name: self.pluginObjs[order].setValue(which, x) def setPluginPreset(self, order, which, label): plugins = CeciliaLib.getVar("plugins") if plugins[order] is not None: if plugins[order].getName() == self.pluginObjs[order].name: self.pluginObjs[order].setPreset(which, label) def setPluginGraph(self, order, which, func): plugins = CeciliaLib.getVar("plugins") if plugins[order] is not None: if plugins[order].getName() == self.pluginObjs[order].name: self.pluginObjs[order].setGraph(which, func) def getMidiCtlNumber(self, number, midichnl=1): if not self.midiLearnRange: self.midiLearnSlider.setMidiChannel(midichnl) self.midiLearnSlider.setMidiCtl(number) if CeciliaLib.getVar("systemPlatform") == "darwin": self.tmpScanCallback = CallAfter(self.stop, .25) else: wx.CallLater(250, self.stop) else: tmp = [number, midichnl] if tmp not in self.midiLearnCtlsAndChnls: self.midiLearnCtlsAndChnls.append(tmp) if len(self.midiLearnCtlsAndChnls) == 2: self.midiLearnSlider.setMidiChannel([self.midiLearnCtlsAndChnls[0][1], self.midiLearnCtlsAndChnls[1][1]]) self.midiLearnSlider.setMidiCtl([self.midiLearnCtlsAndChnls[0][0], self.midiLearnCtlsAndChnls[1][0]]) if CeciliaLib.getVar("systemPlatform") == "darwin": self.tmpScanCallback = CallAfter(self.stop, .25) else: wx.CallLater(250, self.stop) def midiLearn(self, slider, rangeSlider=False): self.midiLearnSlider = slider self.midiLearnRange = rangeSlider self.midiLearnCtlsAndChnls = [] self.shutdown() self.boot() self.scan = CtlScan2(self.getMidiCtlNumber, False) self.server.start() def getAvailableAudioMidiDrivers(self): inputDriverList, inputDriverIndexes = pa_get_input_devices() try: defaultInputDriver = inputDriverList[inputDriverIndexes.index(pa_get_default_input())] except: defaultInputDriver = "" outputDriverList, outputDriverIndexes = pa_get_output_devices() try: defaultOutputDriver = outputDriverList[outputDriverIndexes.index(pa_get_default_output())] except: defaultOutputDriver = "" midiDriverList, midiDriverIndexes = pm_get_input_devices() try: defaultMidiDriver = midiDriverList[midiDriverIndexes.index(pm_get_default_input())] except: defaultMidiDriver = "" if len(midiDriverList) > 1: midiDriverList.append("all") midiDriverIndexes.append(99) return inputDriverList, inputDriverIndexes, defaultInputDriver, outputDriverList, outputDriverIndexes, \ defaultOutputDriver, midiDriverList, midiDriverIndexes, defaultMidiDriver def validateAudioFile(self, path): return sndinfo(path) is not None def getSoundInfo(self, path): """ Retrieves information of the sound and prints it to the console. return (number of channels, sampling rate, duration, fraction of a table, length in samples, bitrate) """ if CeciliaLib.getVar("DEBUG"): print('--------------------------------------') print(path) info = sndinfo(path) if info is not None: samprate = info[2] chnls = info[3] nsamps = info[0] dur = info[1] bitrate = info[5] format = info[4] for i in range(24): size = math.pow(2, (i + 1)) if size > nsamps: break tableFrac = nsamps / size if CeciliaLib.getVar("DEBUG"): print("channels = %d" % chnls) print("sampling rate = %s" % samprate) print("number of samples = %s" % nsamps) print("duration in sec. = %s" % dur) print("bitrate = %s" % bitrate) print("file format = %s" % format) return (chnls, samprate, dur, tableFrac, nsamps, bitrate, format) else: if CeciliaLib.getVar("DEBUG"): print('Unable to get sound infos. "%s" bypassed!' % path) return None def getSoundsFromList(self, pathList): soundDict = dict() for path in pathList: if os.path.isfile(path): infos = self.getSoundInfo(path) if infos is not None: sndfile = os.path.split(path)[1] if sndfile not in soundDict.keys(): soundDict[CeciliaLib.ensureNFD(sndfile)] = {'samprate': infos[1], 'chnls': infos[0], 'dur': infos[2], 'bitrate': infos[5], 'type': infos[6], 'path': path} else: if CeciliaLib.getVar("DEBUG"): print('not a file') if CeciliaLib.getVar("DEBUG"): print() return soundDict cecilia5-5.4.1/Resources/constants.py000066400000000000000000000527601372272363700175650ustar00rootroot00000000000000""" Copyright 2011 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import os, sys from .images import * BUILD_RST = False APP_NAME = 'Cecilia5' APP_VERSION = '5.4.1' APP_COPYRIGHT = 'iACT, 2020' FILE_EXTENSION = "c5" if sys.platform == "win32": FILE_ENCODING = "mbcs" else: FILE_ENCODING = "utf-8" DEFAULT_ENCODING = sys.getdefaultencoding() ENCODING = sys.getfilesystemencoding() if '/%s.app' % APP_NAME in os.getcwd(): RESOURCES_PATH = os.getcwd() os.environ["LANG"] = "en_CA.UTF-8" else: RESOURCES_PATH = os.path.join(os.getcwd(), 'Resources') if not os.path.isdir(RESOURCES_PATH) and sys.platform == "win32": RESOURCES_PATH = os.path.join(os.getenv("ProgramFiles"), "Cecilia5", "Resources") TMP_PATH = os.path.join(os.path.expanduser('~'), '.cecilia5') if not os.path.isdir(TMP_PATH): os.mkdir(TMP_PATH) RECENT_FILE_PATH = os.path.join(TMP_PATH, '.recent.txt') if not os.path.isfile(RECENT_FILE_PATH): f = open(RECENT_FILE_PATH, "w") f.close() PRESETS_PATH = os.path.join(TMP_PATH, 'presets') if not os.path.isdir(PRESETS_PATH): os.mkdir(PRESETS_PATH) AUTOMATION_SAVE_PATH = os.path.join(TMP_PATH, 'automation_save') if not os.path.isdir(AUTOMATION_SAVE_PATH): os.mkdir(AUTOMATION_SAVE_PATH) DOC_PATH = os.path.join(TMP_PATH, 'doc') if not os.path.isdir(DOC_PATH): os.mkdir(DOC_PATH) PREFERENCES_FILE = os.path.join(TMP_PATH, 'ceciliaPrefs.txt') MODULES_PATH = os.path.join(RESOURCES_PATH, 'modules') SPLASH_FILE_PATH = os.path.join(RESOURCES_PATH, "Cecilia_splash.png") MODULE_COMPILE_BACKUP_PATH = os.path.join(TMP_PATH, 'moduleCompileBackup.c5') MODULE_RUNTIME_BACKUP_PATH = os.path.join(TMP_PATH, 'moduleRuntimeBackup.c5') # Meter icons ICON_VUMETER = catalog['vu-metre2'] ICON_VUMETER_DARK = catalog['vu-metre-dark2'] # Plugin icons ICON_PLUGINS_KNOB = catalog['knob-trans-sm'] ICON_PLUGINS_KNOB_DISABLE = catalog['knob-disab-sm'] ICON_PLUGINS_ARROW_UP = catalog['arrow_up'] ICON_PLUGINS_ARROW_UP_HOVER = catalog['arrow_up_hover'] ICON_PLUGINS_ARROW_DOWN = catalog['arrow_down'] ICON_PLUGINS_ARROW_DOWN_HOVER = catalog['arrow_down_hover'] # Toolbox icons ICON_TB_LOAD = catalog['load-normal-trans'] ICON_TB_LOAD_OVER = catalog['load-hover-trans'] ICON_TB_SAVE = catalog['save-normal-trans'] ICON_TB_SAVE_OVER = catalog['save-hover-trans'] ICON_TB_RESET = catalog['reset-normal-trans'] ICON_TB_RESET_OVER = catalog['reset-hover-trans'] ICON_TB_SHOW = catalog['show-normal-trans'] ICON_TB_SHOW_OVER = catalog['show-hover-trans'] ICON_TB_HIDE = catalog['hide-normal-trans'] ICON_TB_HIDE_OVER = catalog['hide-hover-trans'] ICON_TB_RECYCLE = catalog['recycle-normal-trans'] ICON_TB_RECYCLE_OVER = catalog['recycle-hover-trans'] ICON_TB_PLAY = catalog['play-normal-trans'] ICON_TB_PLAY_OVER = catalog['play-hover-trans'] ICON_TB_EDIT = catalog['edit-normal-trans'] ICON_TB_EDIT_OVER = catalog['edit-hover-trans'] ICON_TB_OPEN = catalog['open-normal-trans'] ICON_TB_OPEN_OVER = catalog['open-hover-trans'] ICON_TB_CLOSE = catalog['close-normal-trans'] ICON_TB_CLOSE_OVER = catalog['close-hover-trans'] ICON_TB_TIME = catalog['time-normal-trans'] ICON_TB_TIME_OVER = catalog['time-hover-trans'] ICON_TB_DELETE = catalog['delete-normal-trans'] ICON_TB_DELETE_OVER = catalog['delete-hover-trans'] # RadioToolbox icons ICON_RTB_POINTER = catalog['pointer-normal-trans'] ICON_RTB_POINTER_OVER = catalog['pointer-hover-trans'] ICON_RTB_POINTER_CLICK = catalog['pointer-click-trans'] ICON_RTB_PENCIL = catalog['pencil-normal-trans'] ICON_RTB_PENCIL_OVER = catalog['pencil-hover-trans'] ICON_RTB_PENCIL_CLICK = catalog['pencil-click-trans'] ICON_RTB_ZOOM = catalog['zoom-normal-trans'] ICON_RTB_ZOOM_OVER = catalog['zoom-hover-trans'] ICON_RTB_ZOOM_CLICK = catalog['zoom-click-trans'] ICON_RTB_HAND = catalog['hand-normal-trans'] ICON_RTB_HAND_OVER = catalog['hand-hover-trans'] ICON_RTB_HAND_CLICK = catalog['hand-click-trans'] # PrefRadioToolbox icons ICON_PREF_AUDIO = catalog['audio-normal-trans'] ICON_PREF_AUDIO_OVER = catalog['audio-hover-trans'] ICON_PREF_AUDIO_CLICK = catalog['audio-click-trans'] ICON_PREF_CECILIA = catalog['cecilia-normal-trans'] ICON_PREF_CECILIA_OVER = catalog['cecilia-hover-trans'] ICON_PREF_CECILIA_CLICK = catalog['cecilia-click-trans'] ICON_PREF_FILER = catalog['filer-normal-trans'] ICON_PREF_FILER_OVER = catalog['filer-hover-trans'] ICON_PREF_FILER_CLICK = catalog['filer-click-trans'] ICON_PREF_PATH = catalog['path-normal-trans'] ICON_PREF_PATH_OVER = catalog['path-hover-trans'] ICON_PREF_PATH_CLICK = catalog['path-click-trans'] ICON_PREF_MIDI = catalog['midi-normal-trans'] ICON_PREF_MIDI_OVER = catalog['midi-hover-trans'] ICON_PREF_MIDI_CLICK = catalog['midi-click-trans'] # PaletteToolBox icons ICON_PTB_PROCESS = catalog['process-normal-trans'] ICON_PTB_PROCESS_OVER = catalog['process-hover-trans'] ICON_PTB_RANDOM = catalog['random-normal-trans'] ICON_PTB_RANDOM_OVER = catalog['random-hover-trans'] ICON_PTB_WAVES = catalog['waves-normal-trans'] ICON_PTB_WAVES_OVER = catalog['waves-hover-trans'] # Input icons ICON_INPUT_1_FILE = catalog['input-1-file'] ICON_INPUT_2_LIVE = catalog['input-2-live'] ICON_INPUT_3_MIC = catalog['input-3-mic'] ICON_INPUT_4_MIC_RECIRC = catalog['input-4-mic-recirc'] # Crossfade icons ICON_XFADE_LINEAR = catalog['xfade-linear'] ICON_XFADE_POWER = catalog['xfade-power'] ICON_XFADE_SIGMOID = catalog['xfade-sigmoid'] # Mario bros ICON_MARIO1 = catalog['Mario1'] ICON_MARIO2 = catalog['Mario2'] ICON_MARIO3 = catalog['Mario3'] ICON_MARIO4 = catalog['Mario4'] ICON_MARIO5 = catalog['Mario5'] ICON_MARIO6 = catalog['Mario6'] # Grapher background ICON_GRAPHER_BACKGROUND = catalog['Grapher_background'] # About icon ICON_CECILIA_ABOUT_SMALL = catalog['Cecilia_about_small'] # Doc frame icons ICON_DOC_PREVIOUS = catalog['previous_24'] ICON_DOC_NEXT = catalog['next_24'] ICON_DOC_UP = catalog['up_24'] # Audio drivers if sys.platform == 'darwin' and '/%s.app' % APP_NAME in os.getcwd(): AUDIO_DRIVERS = ['portaudio'] elif sys.platform == 'darwin': AUDIO_DRIVERS = ['portaudio', 'jack'] elif sys.platform == 'win32': AUDIO_DRIVERS = ['portaudio'] else: AUDIO_DRIVERS = ['portaudio', 'jack'] # MIDI drivers (TODO: add jackmidi on linux) MIDI_DRIVERS = ['portmidi'] # plugin types PLUGINS_CHOICE = ['None', 'Reverb', 'WGVerb', 'Filter', 'Chorus', 'Para EQ', '3 Bands EQ', 'Compress', 'Gate', 'Disto', 'AmpMod', 'Phaser', 'Delay', 'Flange', 'Harmonizer', 'Resonators', 'DeadReson', 'ChaosMod'] NUM_OF_PLUGINS = 4 # Audio settings SAMPLE_RATES = ['22050', '44100', '48000', '88200', '96000'] BIT_DEPTHS = {'16 bits int': 0, '24 bits int': 1, '32 bits int': 2, '32 bits float': 3} BUFFER_SIZES = ['8', '16', '32', '64', '128', '256', '512', '1024', '2048'] AUDIO_FILE_FORMATS = {'wav': 0, 'aif': 1, 'au': 2, 'sd2': 4, 'flac': 5, 'caf': 6, 'ogg': 7} AUDIO_FILE_WILDCARD = "All files|*.*|" \ "Wave file|*.wave;*.WAV;*.WAVE;*.Wav;*.Wave;*.wav|" \ "AIFF file|*.aif;*.aiff;*.aifc;*.AIF;*.AIFF;*.Aif;*.Aiff|" \ "Flac file|*.flac;*.FLAC;*.Flac;|" \ "OGG file|*.ogg;*.OGG;*.Ogg;|" \ "SD2 file|*.sd2;*.SD2;*.Sd2;|" \ "AU file|*.au;*.AU;*.Au;|" \ "CAF file|*.caf;*.CAF;*.Caf" POLY_CHORDS = {'00 - None': [0], '06 - Major': [0, 4, 7, 12], '07 - Minor': [0, 3, 7, 12], '08 - Seventh': [0, 4, 7, 10], '10 - Minor 7': [0, 3, 7, 10], '09 - Major 7': [0, 4, 7, 11], '13 - Major 11': [0, 4, 7, 11, 18], '15 - Minor 7b5': [0, 3, 6, 10], '16 - Dimini.': [0, 3, 6, 9], '12 - Minor 9': [0, 3, 7, 10, 14], '11 - Major 9': [0, 4, 7, 11, 14], '17 - Ninth': [0, 4, 7, 10, 14], '14 - Minor 11': [0, 3, 7, 10, 17], '04 - Serial': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], '05 - Whole T.': [0, 2, 4, 6, 8, 10], '01 - Phasing': [0, 0.1291, 0.1171, 0.11832, 0.125001, 0.12799, 0.11976, 0.138711, 0.12866, 0.13681], '02 - Chorus': [0, 0.291, 0.371, 0.2832, 0.35001, 0.2799, 0.2976, 0.38711, 0.3866, 0.3681], '03 - Detuned': [0, 0.8291, 0.9371, 1.2832, 1.35001, 0.82799, 0.92976, 1.38711, 1.3866, 0.93681]} # Menu Ids ID_OPEN = 1002 ID_OPEN_RANDOM = 1003 ID_SAVE = 1004 ID_SAVEAS = 1005 ID_UNDO = 1013 ID_REDO = 1014 ID_COPY = 1016 ID_PASTE = 1017 ID_SELECT_ALL = 1018 ID_REMEMBER = 1019 ID_PLAY_STOP = 1034 ID_BOUNCE = 1035 ID_BATCH_FOLDER = 1036 ID_BATCH_PRESET = 1037 ID_USE_SOUND_DUR = 1040 ID_USE_MIDI = 2052 ID_MARIO = 3002 ID_OPEN_RECENT = 4000 ID_OPEN_BUILTIN = 4100 ID_OPEN_AS_TEXT = 4500 ID_UPDATE_INTERFACE = 4501 ID_SHOW_SPECTRUM = 4550 ID_MODULE_INFO = 4600 ID_DOC_FRAME = 4601 ID_GRAPH_FRAME = 4602 # Fonts if sys.platform.startswith('linux') or sys.platform == 'win32': CONTROLSLIDER_FONT = 7 LABEL_FONT = 7 MENU_FONT = 8 CLOCKER_FONT = 10 ENTRYUNIT_FONT = 7 GRAPHER_AXIS_FONT = 8 GRAPHER_LEGEND_FONT = 8 TEXT_LABELFORWIDGET_FONT = 7 SECTION_TITLE_FONT = 10 TAB_TITLE_FONT = 9 SPLITTER_FONT = 7 LIST_ENTRY_FONT = 9 else: CONTROLSLIDER_FONT = 10 LABEL_FONT = 10 MENU_FONT = 11 CLOCKER_FONT = 14 ENTRYUNIT_FONT = 10 GRAPHER_AXIS_FONT = 10 GRAPHER_LEGEND_FONT = 10 TEXT_LABELFORWIDGET_FONT = 10 SECTION_TITLE_FONT = 14 TAB_TITLE_FONT = 10 SPLITTER_FONT = 9 LIST_ENTRY_FONT = 12 # Colours BACKGROUND_COLOUR = "#494949" GRAPHER_BACK_COLOUR = "#C1C1C1" TITLE_BACK_COLOUR = "#282828" SECTION_TITLE_COLOUR = "#CCCCCC" WHITE_COLOUR = "#FFFFFF" LIGHTGREY_COLOUR = "#999999" GREY_COLOUR = "#666666" BLACK_COLOUR = "#000000" BORDER_COLOUR = "#444444" BUTTON_BACK_COLOUR = "#506077" #"#8896BB" TEXT_LABELFORWIDGET_COLOUR = "#FFFFFF" GRADIENT_DARK_COLOUR = "#313740" WIDGET_BORDER_COLOUR = "#BBBBBB" KNOB_BORDER_COLOUR = "#929292" POPUP_BACK_COLOUR = "#6F7F97" #"#80A0B0" POPUP_DISABLE_COLOUR = "#888888" POPUP_LABEL_COLOUR = "#FFFFFF" POPUP_DISABLE_LABEL_COLOUR = "#333333" LABEL_LABEL_COLOUR = "#FFFFFF" LABEL_BACK_COLOUR = "#666666" CPOLY_COLOUR = "#444444" TOGGLE_LABEL_COLOUR = "#FFFFFF" ENTRYUNIT_HIGHLIGHT_COLOUR = "#222222" ENTRYUNIT_BACK_COLOUR = "#666666" SLIDER_BACK_COLOUR = "#666666" SLIDER_KNOB_COLOUR = "#444444" SLIDER_PLAY_COLOUR_HOT = "#004400" SLIDER_PLAY_COLOUR_PRESSED = "#00FF00" SLIDER_PLAY_COLOUR_OVER = "#FFFFFF" SLIDER_PLAY_COLOUR_NO_BIND = "#FFFF00" SLIDER_REC_COLOUR_HOT = "#440000" SLIDER_REC_COLOUR_PRESSED = "#FF0000" SLIDER_REC_COLOUR_OVER = "#FFFFFF" CONTROLSLIDER_BACK_COLOUR = '#99A7CC' CONTROLSLIDER_KNOB_COLOUR = '#ABABAB' CONTROLSLIDER_SELECTED_COLOUR = '#333333' CONTROLSLIDER_TEXT_COLOUR = '#FFFFFF' CONTROLLABEL_BACK_COLOUR = "#6F7F97" PLUGINPOPUP_BACK_COLOUR = "#506077" TR_BACK_COLOUR = "#506077" #"#6F7F97" TR_BORDER_COLOUR = '#BBBBBB' TR_PLAY_NORMAL_COLOUR = '#009911' TR_PLAY_CLICK_COLOUR = '#007A29' TR_RECORD_OFF_COLOUR = '#6E3131' TR_RECORD_ON_COLOUR = '#FF0000' TR_CLOCKER_BACK_COLOUR = "#506077" #'#99A7CC' PREFS_FOREGROUND = '#EEEEEE' PREFS_PATH_BACKGROUND = '#AAAAAA' # Hue, Brightness, Saturation COLOUR_CLASSES = { 'grey': [0., 0.25, 0.], 'green': [100., 0.25, .75], 'green1': [100., 0.25, .75], # filters popup and freq 'green2': [85., 0.3, .6], # filters Q 'green3': [75., 0.4, .6], 'green4': [65., 0.45, .4], 'blue': [230., 0.35, .55], 'blue1': [230., 0.35, .55], # dry/wet, amplitude, balance 'blue2': [220., 0.4, .45], 'blue3': [203., 0.43, .4], 'blue4': [190., 0.5, .35], 'red': [0., .28, .75], 'red1': [0., .28, .75], # Pitch, transposition 'red2': [355., 0.35, .65], 'red3': [350., 0.45, .55], 'red4': [345., 0.5, .45], 'orange': [20., 0.35, .85], 'orange1': [22., 0.35, .85], # post-processing knob lines 'orange2': [18., 0.4, .7], 'orange3': [16., 0.47, .55], 'orange4': [14., 0.52, .45], 'purple': [290., 0.3, .65], 'purple1': [290., 0.3, .65], # process specific parameters 'purple2': [280., 0.4, .6], 'purple3': [270., 0.47, .55], 'purple4': [260., 0.52, .45] } # ToolTips TT_TRANSPORT = """TRANSPORT Triangle: Launch playback. Click again to stop. Circle: Realtime recording of the output sound to a file. """ TT_CLOCK = """CLOCKER Current time of playback. """ TT_SEL_SOUND = """SOUND SELECTOR Select source sound. Click on the popup to open a standard dialog to choose the soundfile to play. Click on the triangle to open the popup window with pre-loaded soundifles. Right-click on the popup to open a "recently used soundfiles" popup window. """ TT_INPUT_MODE = """MODULE'S INPUT MODE 1 - Soundfile: load a soundfile in a sampler or a table. 2 - Mic: use the live input signal to feed the module's processing. Only available with a csampler. 3 - Mic 1: use the live input signal to fill (only once at the beginning of the playback) a sampler buffer or a table. 4 - Mic (circular): use a double buffer to continuously fill the sampler with new samples from the live input sound. Only available with a csampler. """ TT_OUTPUT_TOOLS = """OUTPUT TOOLS Speaker: Play sound in Player app. Scissors: Edit sound in Editor app. Arrows: Use last output sound as source sound. """ TT_INPUT_TOOLS = """INPUT TOOLS Speaker: Play loaded sound in Player app. Scissors: Edit loaded sound in Editor app. Triangle: Toggle for source sound controls. """ TT_CFILEIN_TOOLS = """INPUT CONTROL TOOLS Speaker: Play loaded sound in Player app. Scissors: Edit loaded sound in Editor app. Clock: Set duration of output to source sound duration. """ TT_GRAPHER_TOOLS = """GRAPHER LINE TOOLS Floppy: Save current line parameters to the disk. Folder: Load current line parameters from disk. Arrow: Reinitialize current line parameters. Eye: Show/Hide current line on grapher. """ TT_PRESET = """PRESET Choose a preset to load. """ TT_PRESET_TOOLS = """PRESET MANAGEMENT Floppy: Save a preset. X: Delete a preset. When saving or deleting a preset, you'll be asked to save the module. """ TT_OUTPUT = """OUTPUT FILE Name of output file. Click to open a standard saving dialog. """ TT_DUR_SLIDER = """DURATION SLIDER Set duration of output. Shift-click or double-click in slider knob to set value from keyboard. """ TT_GAIN_SLIDER = """GLOBAL GAIN SLIDER Adjust gain of output. Shift-click or double-click in slider knob to set value from keyboard. """ TT_CHANNELS = """OUTPUT CHANNELS Select number of channels for output. """ TT_PEAK = """PEAK DISPLAY Displays peak amplitude of output. Double-click to reset. """ TT_GRAPH_POPUP = """GRAPHER SELECTOR Select parameter line for editing. """ TT_RES_SLIDER = """RECORDING RESOLUTION Adjust resolution of recorded automation on graph. """ TT_GRAPHER_POINTERS = """GRAPHER EDITING TOOLS Arrow: Use pointer tool - shortcut = "v". Magnifying glass: Use zoom tool - shortcut = "z". Hand: Use hand tool - shortcut = "h". Pencil: Use pencil tool - shortcut = "p". """ TT_GRAPHER_GENERATORS = """GRAPHER LINE GENERATORS Rand line: Use stochastic function generators. Sine wave: Use waveform function generators. Gears: Use function processors. """ TT_SLIDER_LABEL = """SLIDER LABEL Show the parameter's name. Click to select in grapher. Shift-click to solo in grapher. Right-click starts midi learn. Shift-Right-click removed midi binding. Double-click to set OSC bindings. """ TT_SLIDER_AUTO = """SLIDER AUTOMATION CONTROLS Triangle: Playback controls. - Dark green: Off - Light green: Play with visual update - Yellow: Play without visual update. Circle: Record movements of this slider. """ TT_SLIDER_DISPLAY = """SLIDER DISPLAY Show the parameter's value Click in to enter value from keyboard. Click and scroll on value to increment/decrement, left<->right position of the mouse controls the increment size. """ TT_RANGE_LABEL = """RANGE SLIDER LABEL Show the parameter's name. Functions listed below apply to the minimum value if the click is on the left side of label and to the maximum value if the click is on the right side of label. Click to select in grapher. Shift-click to solo in grapher. Right-click starts midi learn. Shift-Right-click removed midi binding. Double-click to set OSC bindings. """ TT_RANGE_DISPLAY = """RANGE SLIDER DISPLAY Show the parameter's value Click in to enter value from keyboard. Two values, separated by a coma, must be given. """ TT_SPLITTER_LABEL = """SPLITTER SLIDER LABEL Show the parameter's name. Splitter slider sends its values on the mouse release (mouse up). """ TT_SPLITTER_DISPLAY = """SPLITTER SLIDER DISPLAY Show the parameter's value Click in to enter value from keyboard. Three values, separated by a coma, must be given. """ TT_SAMPLER_OFFSET = """OFFSET SLIDER Offset time in seconds into source sound. """ TT_SAMPLER_LOOP = """LOOP MODE Direction of loop. """ TT_SAMPLER_XFADE_SHAPE = """CROSSFADE SHAPE Shape of the crossfade. Linear, equal power or sine/cosine. """ TT_SAMPLER_START = """START FROM LOOP If checked, start directly from loop in point (instead of the beginning of the file). """ TT_SAMPLER_LOOP_IN = """LOOP IN SLIDER Set loop in point in seconds. Right-click on the label to start midi learn. Shift-Right-click on the label to remove midi binding. Double-click on the label to set OSC bindings. """ TT_SAMPLER_LOOP_DUR = """LOOP DURATION SLIDER Set loop duration in seconds. Right-click on the label to start midi learn. Shift-Right-click on the label to remove midi binding. Double-click on the label to set OSC bindings. """ TT_SAMPLER_CROSSFADE = """CROSSFADE DURATION SLIDER Set duration of loop crossfade in percent. Right-click on the label to start midi learn. Shift-Right-click on the label to remove midi binding. Double-click on the label to set OSC bindings. """ TT_SAMPLER_GAIN = """GAIN SLIDER Set input gain of source sound. Right-click on the label to start midi learn. Shift-Right-click on the label to remove midi binding. Double-click on the label to set OSC bindings. """ TT_SAMPLER_TRANSPO = """TRANSPOSITION SLIDER Set transposition of source sound. Right-click on the label to start midi learn. Shift-Right-click on the label to remove midi binding. Double-click on the label to set OSC bindings. """ TT_SAMPLER_AUTO = """SLIDER AUTOMATION CONTROLS Triangle: Playback controls. - Dark green: Off - Yellow: Play without visual update. Circle: Record movements of this slider. """ TT_STOCH_TYPE = """ALGORITHM Type of random distribution. """ TT_STOCH_INTERP = """INTERPOLATION Interpolation method between points. """ TT_STOCH_POINTS = """POINTS Number of points over which to draw the function. """ TT_STOCH_MIN = """MIN Minimum value (mapped over range of parameter). """ TT_STOCH_MAX = """MAX Maximum value (mapped over range of parameter). """ TT_STOCH_X1 = """X1 Distribution specific first parameter. """ TT_STOCH_X2 = """X2 Distribution specific second parameter. """ TT_WAVE_SHAPE = """WAVE TYPE Waveshape of the function generation. """ TT_WAVE_POINTS = """POINTS Number of points over which to draw the function. """ TT_WAVE_AMP = """AMPLITUDE Amplitude of the waveform, centered around the middle of the grapher. """ TT_WAVE_FREQ = """WAVEFORM FREQUENCY Number of cycles to draw. """ TT_WAVE_PHASE = """WAVEFORM PHASE Initial phase of the waveform. """ TT_WAVE_WIDTH = """PULSE WIDTH Pulse width of the waveform (duty cycle when it applies). """ TT_PROC_TYPE = """ALGORITHM Type of the processor to use. """ TT_SCATTER_X = """SCATTER X Amount of horizontal deviation. """ TT_SCATTER_Y = """SCATTER Y Amount of vertical deviation. """ TT_OFFSET_X = """OFFSET X Horizontal offset. """ TT_OFFSET_Y = """OFFSET Y Vertical offset. """ TT_GRAPHER = """GRAPHER BINDINGS Pointer tool: - Click and drag line to move it horizontally. - Shift+Click on line to add a point. - Double-click on line to toggle curved mode. - Click on point or drag to select points. - Click and drag to move point or selected points. - Dragging with Alt key clips horizontal position. - Dragging with Shift+Alt clips vertical position. - Double-click anywhere to add point. - Delete key to delete selected points. Pencil tool: - Click anywhere to add point. - Click and drag to add multiple points. Zoom tool: - Click and drag to zoom a region. - Escape key to reset zoom level. Hand tool: - When zoomed, click and drag to move the view.""" TT_POPUP = """POPUP Choose amongst a predefined list of elements. """ TT_TOGGLE = """TOGGLE Two states button usually used to start/stop processes. """ TT_BUTTON = """BUTTON A simple trigger. Both mouse down and mouse up trigger an event. """ TT_GEN = """LIST GENERATOR List entry, useful to send list of discreet values. """ TT_POLY_LABEL = """POLYPHONY VOICES Number of independent notes generated. """ TT_POLY_CHORD = """POLYPHONY CHORDS Pitch mapping between individual notes. """ TT_POST_ITEMS = """POST_PROCESSING Choose a post-processing module. Parameters appear on the left buttons. Signal routing is from top to bottom. """cecilia5-5.4.1/Resources/images.py000066400000000000000000003441511372272363700170140ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ from wx.lib.embeddedimage import PyEmbeddedImage # ***************** Catalog starts here ******************* catalog = {} index = [] #---------------------------------------------------------------------- arrow_down_hover = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAYAAACJxx+AAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAANrAAADawB7wbGRwAAAI1JREFUGJVt0LFqQlEQhOFvw32CkMbeWPoMBgRfUssU6fMi' b'WhhCmlQptLA11VjkXDloFhaWmX+WZQsrfCf51FVVzTAdcMK+qnb4av4z5lhIAlvkpj+SeGiJ' b'jftaw7jhEecu/YunJH9Ag1474O2qd8BLByz/AwoHHFGjPowXJUlVvTcz1390s6qaNO1n1C6G' b'EFt0TbKPGQAAAABJRU5ErkJggg==') index.append('arrow_down_hover') catalog['arrow_down_hover'] = arrow_down_hover #---------------------------------------------------------------------- arrow_down = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAYAAACJxx+AAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAANrAAADawB7wbGRwAAANpJREFUGJVdjLFqg1AYhY+mKb24uPgUvkAHXySDiHsHpy61' b'D9CpOHXRQAbfwUkaBLkogoIZs7WCg53bNO3p0iuSb/r5zvmP5nnes2maxyiKXkj+4B/XdTck' b'bzXf9+/jOH4qy/I0DMOHEGIthDAcx7kJguARALQsyz55QZ7nXwB0nSSllK+4QEq5J/mrA0BV' b'VeE4jnM4TRO6rnsAAJAESSRJ8qbm0zR9V15XX03TbBf3bp5TTQDroijOdV2fbdu+Vv5qUfwO' b'w/BgGIbW9/1J+bkAAG3b3lmWtVq6Pyhsir01ukkfAAAAAElFTkSuQmCC') index.append('arrow_down') catalog['arrow_down'] = arrow_down #---------------------------------------------------------------------- arrow_up_hover = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAYAAACJxx+AAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAANrAAADawB7wbGRwAAAINJREFUGJV1zaEOQQEUBuDv2BUEm2ATCcwUTTPdMwieS7Sp' b'3kXFA2iioBzBxb3Gv/3l7DvnyEyvoo9ubfYFdtj8BGjgiss/sESWnf8C+wrY1gB6uFfADZ3M' b'1PDMGk2ftLDC+8Kxsv3qITMVEbHAEAecywsjTCNiVqCNSWaeKi9ExBiDBzGacT4RANd0AAAA' b'AElFTkSuQmCC') index.append('arrow_up_hover') catalog['arrow_up_hover'] = arrow_up_hover #---------------------------------------------------------------------- arrow_up = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAYAAACJxx+AAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAANrAAADawB7wbGRwAAANlJREFUGJVlkKFuhEAYhGcLprIGe8iKlqRyKZIgeAMaDJcN' b'D1DfpPJeoB5XiV8M5iwOjSIpCakgubQJ6U5N2Vx6n5pM/v8TI0hiI0mSRwDQWh+3zsUZQRC8' b'GWMI4OHiQAjhNE1zt64rhBAOyR8AAEmQhFLqlSSNMczz/GXrrzaDlHL/Z7LZGtI0vZ+miRvD' b'MFBKeWsNYRgePM+zT77vI4qigzW0bfvNf9R1fSIJZ57n57IsU631V9d1n33fn8ZxdOM4vs6y' b'7MNdluVGKfVUVdX7+SZFUexJ7n4BAlmFA6WrLtoAAAAASUVORK5CYII=') index.append('arrow_up') catalog['arrow_up'] = arrow_up #---------------------------------------------------------------------- audio_click_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAc5JREFUWIXt1j1oFUEcBPDfS6IIilUKESTs2ogICokRJKJC' b'kPMDCwvFzkYshaA2goJWEhAE26hYRGytFrGwtLFQOw132lhEqwh+EROLnBDExLd5FxSSgYPj' b'vzdzw3A7t63Z2Vn/M7r+tYG/YdVgp1g12ClWnsEU4oMUYmPd1dOUUAqxDzew97f5ZjzBF+wp' b'qnI6R7eRBFOIu3EVp7Bl/lpRle9xBs/wMoV4MEc7K8EU4roFOFdwbAHOSFGVN1OIk9iJEynE' b'vqIq7zVqMIXYjQvY/4flwUWooynEKdwvqnIohfgQd9GsQazFIezL4MBhnMd2jGAqh5zzDfZg' b'Mkcciqp8jG04Wo8+5/BzEpzBUutjCp/q++4cYo7BVn0tBeuxpr6fySHm9mB2b9ab4ike1aPe' b'5XrhD2zIEa9xAMNFVb6qO/C7uU5sCzkGv+G2uTR68BXT2ISz2LgAb0dRlR9SiCcxjsvm6qot' b'tJo48qcQr+M0tv6aFVXZmrd+EUN4jvGiKifa1e74V5dC7MIobi3y2CXsGhvov3bk3duJVqv9' b'vdZIgpBC7MVxnMPg/AQ7QWPHrbGB/o/Db17fwYumNGkwweXCyjtRN41Vg51i1WCn+AlFl3Lb' b'+CmHgwAAAABJRU5ErkJggg==') index.append('audio-click-trans') catalog['audio-click-trans'] = audio_click_trans #---------------------------------------------------------------------- audio_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAdNJREFUWIXt1j1oFUEUBeDvJVEExSqICJLCYkQk2qggURSC' b'rD9YWCh2FoqlQ1AbQUEricgqtlGriJVgNYiFpY2FisVi2qSIVhH8IyYWeUIQE9/kbVDIOzAw' b'3Nlz5nDYvXsbs7Oz/md0/WsDf0PHYLvoGGwXK89giOlRiKm23tVTl1CIqQ83sfe3+iY8xxfs' b'qcpiOke3lgRDTLtwDaewef5ZVRYTOIOXeBNiOpijnZVgiGnNApyrOLYAZ6gqi9shpknswIkQ' b'U19VFg9bubPlBENM3biIJ39Y+xehDoeYzmKiKosBbMCDVu/NSXA1DmFfBgcO4wK2YQhTOeSc' b'd7AHkzniUJXFM2zF0Wbpcw4/J8EZLLV9TOFTc9+dQ8wx2GiupWAtVjX3MznE3D6Y3TdDTI/x' b'Ak+bpd7luvAH1uWIN3EAg1VZvG32wO/memJLyDH4DffMpdGDr5jGRpzD+gV426uy+BBiOolR' b'XDHXrlpCo46RP8R0A6ex5VetKovGvPNLGMArjFZlMdaqdtu/uhBTF4ZxZ5HHLmNn//jI9fd3' b'j4w1Gq1/a7UkCCGmXhzHeeyen2A7qG3c6h8f+fju1uB9vK5LkxoTXC6svIm6bnQMtouOwXbx' b'E0PNduXRvLOYAAAAAElFTkSuQmCC') index.append('audio-hover-trans') catalog['audio-hover-trans'] = audio_hover_trans #---------------------------------------------------------------------- audio_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAaFJREFUWIXt1j9rVEEUhvHf3UQRDKkUxEbBRsTWNCoaEEGE' b'VFnBzkasRAhYiKUfQSxNYRG11c7KwsImhQipbGxNqggisslrca+QxHXdyd6gkH1guPOHc+a9' b'w5lzpkrif6bzrwX8jbHAURkLHJV9KfA5WstdbQo8gRc4v2P+OFawjMlir0naaOeSLGY7W9dn' b'kjxJspJktsR3qZBDSab6tNf5nV82C833ZJJ3SR4nuTXsnlVBqZvAA1zqszaD6R1zVfPdwB08' b'ww+8xI0t6wMpiYmDuIqLBTZwDfdwBgtYLzEuuSST+FLivOENTuN6M/5WYlxygpt2nz7W8bXp' b'T5QYlgisDBk3fTiMA01/s8SwNC+V57H6UrzFq2Z8ZK823MBUifOGy7iCj5hV3+T3wxqXpJkO' b'5nBW/WPf0cMx3PbnNHMUq+rUsoSHeIq1tgUO4hFu4lQfgXAfF9TlbgmfhvbcQpnrJJlOcndA' b'qVtN8nl+fl5VVUX7tnWC1ME/p64aM3Z/47fR2mum2+2u9Xq9RXxoyyftxeCesS9f1K0yFjgq' b'Y4Gj8hMzObAudOMxgQAAAABJRU5ErkJggg==') index.append('audio-normal-trans') catalog['audio-normal-trans'] = audio_normal_trans #---------------------------------------------------------------------- Cecilia_about_small = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAYAAABxLuKEAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAE6lJREFUeJztXHdwXMd5/+3ua/eu49BBEoUg2KtskjIpWxQd' b'ObQilzElxw4tF9l/OG4TpUwmE3smdWI7HhfZVhwVS5Y9zjhudEjLjbZkkWJRYYPYQJDo7YA7' b'XH/3ym7+eAfwQALE3QG0lJl8M/gDr+y3+3tf32+PCCHw/3QjSa8R31A+byzJpNOrJhOJJULw' b'CEA0ABQQthDIaaoa9QcC3V6vt1uS5CEAmT/kBP9QwIRHR4bvvHL1ynt6evt2DI1E6yeTGTWb' b'y5O85QCUgRIGEABCgDsOGAUUmcLn1XhVKGA01ddebm1tea6tte3HwVD4GADjVk6Y3EJVkgYG' b'+u9/6aWXHuzq7tk2NBb35m0Cpvogqx5IsgJKGQghILO8LAAIzuFwB7aVh2Vkwa0svBoTzU21' b'k2tXdzyzZcttjwSDoSOFxxeVFh0YwfmyV891PvT8kaN7z3f1NdpEIx5fGIrqAaEEEALl8yQg' b'xIWPc458LgMjE4Nfg7N+Vfv5O+7Y8WRLS9t/YBHVbTGBCZ0+dfIff3nodx+5OjjhVb3V8HgD' b'IIRACL5YPFwiBAQEjuMgk4qBmAlsXtc+9Na77vrSsuaWhwFYC2axGMAMDQ782U/2/+wLnV0D' b'jVqgHh7dB6ASySifCKEQQiCdnADycbFz68bj99xzzyd9Pv/LCxp3IZPnnLf+/vfPfuFnzxx6' b't8VCzB+MQEAAr0EIQAgFFxyJ8SHUBmjmvne/41vr1m/4JwCTFY1XKTCpVPLup7/7vSdPX+xv' b'CNa2QGLS4qtMBUQog5HLwJgcwNt37zi+Z8+e9zMmXSl7nAqAIcNDgw88+u2nvjGWIt5AuO51' b'AUgxuXZNID7WhzWtNZcf/MiHP+b1+p4ta4wygaFXrlz+1H8+8fTnDRJSdX8Qgr++QCkmQhgm' b'JwbRVu8d/ehHPvT+UCj825LfLQMY0tl55u+/9cR3P6eElkmqqr/uJGU2IpQiNTmOsJIb/4tP' b'f/KBcFXkmVLeo6Uy6O/r/djjT33/c2qoRVJVz/8JUAA3SPSHqhE3PdWPP/md72Qy6TtLea8k' b'iZkYj77ri195+HumVKNrHn1R3DAhpPBHpyNfARQBTmZExVP3REUBois5iYkRrGj0Dn3izz9+' b'pywrXTd9fj4mppm/7evffOQXPVG72heoWpCkEEJAKYMQAqZpIJNJIZWeRCo9iVwujbxpwLFt' b'CAhQyiDLClRFg+7xwav7oet+6LoPiqKBMTfNE0KAc6c0/pQhPtqLXdtWn7v//ve+GcDEXM/O' b'l0RW7d+//+mugWR1uGYphChtAtfTVE6UyaQwMtqHwcEriCXGkDMycIQDQdwvSghx0wY3m3Ql' b'QwgIARABMCpBlVV49QDCwWpUVdUh6A8jGIyAUjbvPAR3EKpdit8dPbWmra31y294w9YH5nr2' b'phLT2Xnmi998/Pt/FaxrRyV5Gi0sNjYxhq7uM+gf6kYunwWRGWRZBpNkEEpmqAngSoELEgUl' b'tJBjuc9wh4M7NhzbgXA4CAfeeude1FQ3lig5BJZtguWG7b956NP3RCLVv5rtqTklxjBym3/0' b'0wMf94Sapj5gWcSYhGQyjs5zx9HTfxEOHCgeD3RPEFRyAXO4A3ABXfUi6KuCTw9CkRRwwWHk' b's0ilJ5HIxJG3DEiyDCYpkJRCYF1QIcvIg5clyQKKoiKR9kgHDh585IMPfHATgFSpwJDnnnv2' b'q6MJyxuq0cuKVaYMalfXGZw8exiGbUDz6tAUBZQxEEohBIdj24gEarF86WrUVDVAVTwgtMhJ' b'FhaezWUwONqDyz2dyBhpKJpWUDsKIogrQZzPWrqYExrOEQjX4sTprrbtF8//68qVqz9VEjAT' b'E+N7f/Pc0R3+8JKyQeGc45VXfocL3acg6xq8viCYJIEwd9HcccDAsHbFNrQuWQlJksG5Ay44' b'4NzIS9f9WLl8I5Y2tOHUuaPoH+2G4vGAMoYpUXYcG2UhUyDFV4MDP//lR9vbV3yeMWmg+N5s' b'cQw7dOi3n8vaKmVS6QU+QtwywJEXnsH57lPQAj5oPi8kRQaVGAgBHNuGJmm4feNbsaJlHSih' b'cBz7pu5XCA7HseHxeLF9811oa1yFfCYL4bjqIwBYtoWykRECHt2Prr6odub06b+7/vYNwCST' b'iXe+eOrVtb5QbcnSMpWbHDv+K/QMXYIeDED2aKCSNK0eju1AZSq2rb8LNZEGF5AyDBfnHCAE' b'm9fvQF24EfmcMT0/06qsyikg4AnU4rnDL7wPgLf43g3AnDhxfF/GpISx+d3f9CCE4tTpI7jS' b'fx56wA9ZK9iTQtVNcA5wgU2rbkdVuNYV/UoWIgQYk7Fx7ZvABIFj2YAQyJsVln+FgKb70d03' b'Grp6tfuDM9ZU/A/nPPjyqbN3efyRkgM5xiT09Xfh3KWXoPl9kDR1BigQApZlobVxJZrqWioG' b'5docHVSFa9Hc2AHTMCAgYOSzFUfjBICQvDh67PjHUaSPM4AZHOj7QP9ILKhoemmDEgLDyOCV' b'08+DqTIUTXONIrmm75xz6IqOFS3rwBepgCWEQGvzKlBBIByOnJGpOMsXgsPjC+Nid+9q0zTX' b'Tl2fAcyZs533cuop2YxRynCp6zQSmThUXS8Y2eK3BWzbQktjB7x6YNEST84dhILV8HuCyKXT' b'yBs516tVSLKsYDyeYd3dXe+culYMjNrV3fNGTQ+WtICpEP9S9xmougdMlmbGIQA4F1Cogqa6' b'1kWv2zDKsGXDTmxZuxO3bXgzKC25UDA7STouXrw0Dcy0P04mE+uHRseDim9JyRPr67+EbD4D' b'byh4AygAwB0bNcEm+H3BBX3RWYkADQ0taGpsm46DpuKockkIAcXjQ9/g8DoAIQCT08CMjoz8' b'ccZwaDBAS5IY27HR03cRkqrMcMvFxB2O2kgTKGULNrrF5Dg2ksmk+w9xk07BHXT3vIr21vUI' b'Bkt3Hi4JKIqGsfGoJ2/kOlTNc2IamGg0uglULmkYSikSiQlMJicg68qsYiyEACMM4bInOR9v' b'hlhsDM+fOAhWCAsANyaxzDxaW9aAkPI3KiiTkE5aiMfjq+obioAZGRvrIJKKUrJFQihisTHY' b'3IIi6QC90VwLzqFICnSPf/H3lwhAVRnV1Y3QVA8EdzNzwgGPWlkhjQCwHGB8Ynx9fUPjtI0h' b'qVS6kUlKybHoRGwUhFFQRkFmcWNCcGiaDkVWbsnGG6EEK5rWoK56yYxyAy8qX5Q/KENsItYM' b'XPNKeiqd8TAmlVRe4JwjkYoVuecbkeFcQFN0MHprGioEFy4IcKfMhVi4gScMiWSyGrgGjEoZ' b'k0Ao5keGwHFsGEYWjDK478w6dSiyglnFaZGIcweZTALp9CQEd0qq4s1NAoQyUEp9wDV3zQBK' b'S1kDIa5XsGyzUBeZg00hryG3CBjuODh7+SXYtgnbshDwhrB6+RbU1TRV5LIBNzYDgQJcA6Ys' b'pZyun9xs0eLWCAvnHMFAFTZ0bIOsKNBUHfFEFF2D53Dy4gvY4fkj+LyhhXhCAVwDxhGCO0KI' b'sgzCfAvnnN+CDX5XEttb17mFcgD11UuRtwz0RC9jZGIAHb4wnArYCsEBgTxwzcYYQnDLRXn+' b'z0xdXbzpmglxi863ou/B5S3gcAecOxAQCHjDAAhyZrZCnmTK7aeAa8DkggF/htullQgZZZCm' b'q9Jz8SHIm8ai50iEEIyNDSIaHQSZNvyuQ+DcgSKplVQ5XeI2wuFQFChKIgN+X59j5zF7R9w1' b'EkKAMgmaooFzPic2lBAY+Sxsp4Ky402IEILOCydw7OQh5PM5yJIC7tgYnRgEJRSRYG1ZlcEZ' b'Y4OjqipyFShKImuqIxe5bb6xlAEYY/D7w4gmR+eUGkIp8mYOedOAV5cX1dR4dC8mslEcPfUb' b'VIfrMZkYRyw9jvZla1Adqqs8kZSA6kjkJFAkMQ31DS/RMlrXIuG6gk7OPglCCUzbRDqTLBL5' b'hRPnApvX7cDtG3ajOlwHw8wi6K/CmzbuxprlWyAqlE7bsRH066KqqqoLKJKYuvr6X/u9quM4' b'DqOz5D7FJARHpKoOjEjgDgeVxCzxCoGAQDwxhoa6ZRVNdg7uUFUPljYux1LSXuBEwAUveQ/7' b'BiIEVj6HmsZgmknyZaBIYnTde3FJfU00b2Qwn03gnCPgDyPoD8O2TIDfqCeEEFCJYSQ6ANte' b'cBPlDJrySFMG16358oqDSQIC00ijZVnTCRRaYotl3FnVsfywmU2UxECSFSxrWgHbtObUacYk' b'xFPjmExOLDBcn50Yk9B9uRP7f/YEjrzwTOEDlA+OAEC5gdWrVv9w6toM5V+3dt1PZWqJUrJT' b'zjmal66ER9FhW9asbplQCgccfYNdi54aEBBYlonzl15B1s5gKNqLvGlUxMcyDTRUB6xlzc2/' b'mLo2A5i6+vofti2tjxq5NOZDXggOny+AFS3rYBkGuHNjlEsIgawo6BvuRjIZW3hdtogYYxgc' b'7EYiE4fH74cvEIIsKSi3+4AQimwqhjUr208wJvVMXb9upiS/9bbNB3Op8UKfys2Jcwcdyzcg' b'6A3Dyhvgs9gaShlMJ48L3aewWPEMIQSmlUfn+RfBVHcL2OPxQpbksmsxXAhIPCe2bd36pRnz' b'vv7BDRs2frvKJzm2ac47qBACqubBlnU7IWwOx7JumBihBLKqone4C/2Dl6c7oRZCjEm4eOkU' b'YqkoFI8HIAQ+PQBa5tiEEGRTcWxY3TbU2LTkYPG9G4Dx+nyH37Jj2+H05GhJ8YfjOGhoaMH6' b'jq0wc8as4FDGQGUJJ189gonYyILAkZiE4eFenD1/HKquY6rxIOivKtu+CBDYuRje8uY7HgMw' b'QxJmW7nYuXPn3wY0blnW/FIDuPWZ1R1bsLptE8xsDo41s4OBEAJJlmEKE0df/jUmYqOQKgBH' b'kmREx4dx5NgzEBKBrGmgzO26CvojZakRIQSZZAzrOpbF29tXfPn6+7OKhNfrO3b3XXf8JDne' b'P+u2yGwkILBh7e1Y3bYZVjYH2zRneCpCKRRVQ87J4fkTP0fvQFchS5/fjVNKwZiEgYFuPHt4' b'P/IiD83rBZPcRkdVVuH3lVeD4VxA5GPi3rfv+WdCSOL6+3P24Nm21fq1r3/jSN+EaNB9gZK+' b'xlSL6pWeczhz4Tgs2FBUDYTRGZ0PVj4PJ29jSV0rVrVvQjhcA0YliKITK1Njce4gkYjhwsVX' b'cKXvPIjCXBWSJRBCYFsWaoL12HHb20qWGEIp4mP92L19zYm9e++7A9ep0U2BAYC+vt6HvviV' b'R/5dr1lOSqp7FogxCZOTUXRefBFD4/0gEoUsK9NdVRACju3ANHKggqIqUIPaSCNCwWp3OwSA' b'mTeQSE5gLDqIaGwYFreg6h5IigIquY0DgnOYhoHb1tyB5c1rStrUI4TAyGUQVjLpv/zMp7fr' b'Xu+rsz43D8rqoUO/PvjfB57bXVXfVpaoUkohuMDIWD+6ejsxHh+BAwdMktzWM0IguNsmZpum' b'2+vCC3XXQsVfCAEqMciKAibL02rNhQPBBSQqIeyrxtZNu6AqWkkSI4RAauyyeOgTH/2X5e0r' b'PjvXc/M2QAvOWx574vFfnLo0ujJY3QRRZqLGKIPDOSYT4xge68XI+ABS2YRbpyG41tcrgBmH' b'vwSmwx4Bt4wqUQmaoiMcrEFtVSMioVr49ECh4bEENSIU8eHLeO87dx/atWv3n+AmB05Lapk3' b'jNwbv/bwN348MCmW+AKVbbnSQqelbVvIZJNIpuJIpONIZxLI5bOw7DzsQlIIuM3OsuwWu316' b'EAFfCEFfGF5vAKqiuSfbOC95L4kQithYL3Zsbn/lA/v23Q2QObvCgTJOn8RjE3u+9NWHf5Di' b'AZ+3TA8w2yRpQVKmWt4d51r9FnDb1xhlcLe7XBWq9CwBIQzxaB+2rW/u+sC+fe9gTLow7zvl' b'MBkaGrzvm9967NGU4w16A5Gy1Wqeqdyw67A4hzkoJscHsG55zbkHP/zhP1VV7WxJ75XLfDIe' b'u/eRRx97YmDCrg5W1b+Oj+e4rfjx0at406b2c/v27dvLmHS+5Lcr+SrpdGrrU995+kdnL48s' b'CdUuAy20s75eiBAKyzKRmejF23Zt+/2999z7IcrY1bLGqHRBjmO3Hzhw4Ae/evbYZjXYBE33' b'LbJqVUJuUJhKjENDmu99x54fbNt++4MAsmWPtMAvrb/aefazP9p/4FPDcdMbiDSCUfaaqBeh' b'FGY+j3RsAOs7mgbuf897/qGmtu4JABVNZlEOpBu53PoDB//n0eePndzK5SDxBqoLu4W3GiD3' b'fJNtWUjFRxDxEnvP3bv+a8eOOz4DILagkRfRNtCR4aH7fnPot//28tkLLXnhgTcQgSy7VbXF' b'tEGEUDdtMLLIpsYR0infsXXz8V137vprr893ZFF43AKjqY2MDL/v2PHjD758qnPreMKQZS0I' b'1RuAJMmFYFYUqqCl8J5y466Bt8w8jGwCwkpjaX0484bNGw9t3779Kz6f/9kSByyJbuXPpMDI' b'5drOnz/3yTOdne+60ju0dCKRkThRICk6JFkFk2S3vR6kEP5f6yoUws2jHNuCbebg2DmoVKAm' b'4jfbW5ed27Rxw5MdK1Y+BUIq+omC+eiWAlPMxzTzywcG+t/S09N77/DI6OaJ2GRtMp1VMlmD' b'cpBCXcb95RDOHciMQvcoTsCn52trIoOtzc2Hm5ubf1pbV/ciIXT4lk/4NYw/goI7tdlcLsI5' b'DwJQ4RbOHAhhyLIcVzVtnBAaxR/4Z5iA1xaY1zX9L1J0cGN8ZkP0AAAAAElFTkSuQmCC') index.append('Cecilia_about_small') catalog['Cecilia_about_small'] = Cecilia_about_small #---------------------------------------------------------------------- cecilia_click_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAdCAYAAAC9pNwMAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAABUtJREFUSInll0tsXFcZx3/fuY+545nJZDw2fsR1sEdpE1Oi' b'gEIRIaqC0gpoQAqO1EVVVY1UFqVNVcmoimARsoFIrRoEC7LiobJAIaqSSk0FERJGkUXs1AoN' b'hBCl7dhuYwc/JuPM3PHcxzksPLacNjgOBWXBt7znnv/vfN/9HueKMYZ7YeqeUO8l2P4vaKQi' b'aK/VaslUMukrmAL8/xXY/evly0+ePXNmf3F0dLsulRI3ZmaktbPT2LncQueDD458affuX27t' b'6/s1EN5OQO4yueTdYnHvTw4d+mHl3LnNG6KIlkQC17axlSKKY+pRxGwQMC5Cx6OP/n3/wMD3' b'7i8UTgG3gO4KfPKNN15+/dChgS3VqrSvW4cWIf7IfgGUCMoYJstlLnqeeeLIkZe/uWfPwZXw' b'tYLVb0+cOPr6wMCBr+RyohwHbQxiDBiDAbTIkiDKGBBBLAsdhgzOz5v+V1758b7+/oEl+JrA' b'Fy9d2n9k376f7/I8sCwMIFqz4DhUcs0EzXki10VEcLTGrlZwZ2fJzJcREUwc88d6nYMnTjy9' b'ta/vV2sCR1qvf+mppy51nj/fsT6TQRsDWjPXnGfhgc14+Tzq+hT25DVAiDo3wIYu9MQYubfP' b'IyIoEcqVCle2bLn20+PH+1zbLt+xjoeGhl7yh4Y68g2oaM1cvoV4+xfo6C2QHR8nOzJMZmKC' b'zMQ42ZFz2ONFnKYUNMKvjaE5nYYLFzpHRka+C3duINbQ6dNP9zgOUeOb1hIJos98lq6uLlLV' b'KupvFxfDaVkYy0IZg33lCtXS3K2RM4Ye22bw1KlnAGtVcBBFfZOjo+3rPW8xI4ym+qk28hs6' b'yWay6LEiBMGyZwBGKRLVCsnxccrZLHFjzQC5pibGhofbFoJgy6rgmbm5QjQ9LY692Gc0Ai2t' b'ZNPrcCxFNDONqI9LaBGm29qoeR6y4rljWVjlsszduNG7Krhy82ZzXKste2SUwkml8FwXSwSJ' b'Im6XmmIM6TjCS6WRFdFAhNj3qdy8mVsV7LhuIPaKrmoMSkApBQha5BaPAASDsSySpRJhJkOY' b'SCzW+9K6beO4bn1VcGtr6wduczNxHDc80eD7RFojloWVzfLRcjQIGMNCMok7OYlTq2EaXsdx' b'jJvP09ra+uGq4LTn/SVTKIR+EACgDKipKfyajzEGt3sjH3MZsOKYykIdqzSHteJgfhCQKRSC' b'tOe9c6dyKvXt2nXmWq2G3SiZxNQk88X38esLJHp6obMLwpClNqzimPlMBl3YhOW6yzlgi/BB' b'tcqmHTt+B9y5gex+7LHD13K5OI6iRYE4Rob/zPS7VwmVwtn5MPX7uomVTSyKcr6F2rbPk0+n' b'SNTry+AoDPlnW1v0jf7+w7CGG0hbPj+8+/nnj4/OzOCKLGZ2pYL+w++ZPfsnglqNePtD3Nix' b'k+mHvoi/7XMkLQuv+D5WGIIIrggXSiW++sILv2nJ5d6GNQ6JMI47vn/gwFnrrbd6729pIWxM' b'JbQmdl1MKkXkuBhjsIM6lu+jogiUwhHh8vQ0zt69Vw+/+upOS6nrawYD1IJg4+EXXxysvfnm' b'xq35PFjW4ixeGo9LJoIRwRKBOOad2Vm8PXuKPzh69OFkIjGx/NrdXAQirTf+4tixnw0dO/a1' b'T/u+tKfTOLZ9S8vEGMIoYrJS4arrmi8/++zpbz/33HdspcZXat3t1QeAq8Xit06+9tqP3hsc' b'fEBdv47TyPpQa8KmJkx7O5sfeeQfX3/88YObenpO3k7jPwI3zFqIom0TExPbPxwb6wWSBqpd' b'3d3F+7q7RzzbvgDof7f5k4A/kf3//Un8C1j2Wltb4vwkAAAAAElFTkSuQmCC') index.append('cecilia-click-trans') catalog['cecilia-click-trans'] = cecilia_click_trans #---------------------------------------------------------------------- cecilia_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAdCAYAAAC9pNwMAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAABNxJREFUSInllltsVFUUhr+99zlzzvRehlJotGVaKwgRBGtC' b'BPESY+KFRB+IhIiGGJQY3zThAd8MD4g+EQ3RGIkkXp40RCEBo2AAAYlcZNQWKPfSMr1Mp7eZ' b's8/Z24eZDI1ibfHBB9Z5O9lrfftfWWvtJay1/B8m/xfqbQl2btVR66CtvePc2jOdFx/LDA63' b'SiWViUxUU11xprW56fs5d7d84rqxY//kL6ZaXFEUzd9/4KfNu787+PSlrl4iKxBCIgALWGtQ' b'wnJnQ4KnHl/27cPLlmxQykn9J3A63bvqw+1ffHo8dd51vTiOUkXc38ISRhE6P8Z985P61bWr' b'XqybPv2LWwL39Fxfs+m9bR9f6xtxfc8HLEIU1FprMMYU/ymklGAtxlpy+RyzEuV64xvrX66v' b'n7FjSuAgCJZseveDg7+d65a+5wEWKRVjuWH6M90MjvQR6BwAjuNR5ldQXZ6gtnoGIMjlc8xr' b'mWU2vvna0lgsdhgmV9Vi5669W1MdV0pQISTX0hdIdR7hcv9ZUJL6RCMzEo0IR9CTvUx35lLR' b'3eJ7PqmOK3Lnrj1bATEp8NDQ8Kq9+4+2eX68oFRIutMXuJRuR8QVd9TfxdzGxcyqS9JQl2Tu' b'nfeTqJyJwXAjmxbPj7Nn39G2oaGhVZMCn/g1ta4/M4qUAiEEI2NZrvafx6ssp6oiwayaJgQC' b'YyKMiVBSMbO2EUJbBAsApJQMDI5y4lRq3WTA5SdPty8XUhVyLiS9mWuImMD1PGri01DSwY6r' b'bGMN5fFqZjfcQy4YxdiopFpIh5OnO5YD5ROCrTXJdG9GKVUAGxMxks/i+j7SUXhO2U2byVjD' b'tf4L9A52YY1BFFUrpbjeN6CstckJwVrrmr6BTKE9AGMMhgipFEJIlFRwk64QgOO4lJVVIqUq' b'ZURKSf/AIFoHNROCpZSB78UY33JCSIQQpYsUxfzNEpUzGQmyBGGudN5ai+/FkFIGE4Idx+1K' b'1FYXhwMoqXBVDGMNFksQ5UtpvKFWYLFkhnvxZJyY45UubowhUVuN47hX/624riSbGq6GkS6p' b'rSpLEAUhWMuIHuSvA8hiUdJhNJslrspR6sY7FIaaZFPDVeBfwbQtWvC5KwtarDUkqurxhEeo' b'NcPBENmxfpRUiOKnlEN/poe+4R6EFONGucBV0Lbo3s9gEn3c0jz7nYXzmrP5fB6LxXFiNCZa' b'UVqidZ7LA+e4nulCRwFhpLned4ULPb8Tr6gg7leUMpIP8iycl8y2NCe3TAoshEivXrlii++C' b'MRZrDWV+FS1185kWqwMLF9PtpM4f4dS5Q5zvTuHGPGbWNuE6HhaLMRZfWVavXLFFCJGGyb9O' b'3v4Dh3Zv2/7Vo9L1kUIUK1Wgwzx5PYbWeSzguT5+rAylHGzxhTI6x/qXnvvh4YcefBLITwUM' b'4O/78dDej3Z8vSzEIea6gC1UtbhR24VloBAz0BqF5pU1zx54ZPnSJ4CxUianuIGUdZw5+/aO' b'L3e+3t7ZFUM4OI6DlBIhCrPEGEMYhmBD5jQ3BC88v+L9Oa2tbwGj4wNNefUBMCaa+8vxU5sP' b'Hzv1TOelLpnJjqB1iOs61FSV09zYYJa0Lfhm8aIFG6RUf9wsxi2Bx1lNGOoHBrNDzTrQFW7M' b'Ha6uqux0HPdnIDOR438F37Ldfgv9n0R6M7hH0MziAAAAAElFTkSuQmCC') index.append('cecilia-hover-trans') catalog['cecilia-hover-trans'] = cecilia_hover_trans #---------------------------------------------------------------------- cecilia_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAdCAYAAAC9pNwMAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAABIJJREFUSInlVk9sFFUc/mZmd2Z3u2wX625pUiK029L634QW' b'pJRUm4AIaMJFICkJMdWYePGgXDAe9MKhmhgTMYEYDmpEo6JVREnFtFXSVozdJRaYLSnt9M/u' b'zi7bXdudmTfz89A43bZLu9QDB37JO7zvve/75v3evN97HBHhbgR/V1zvSWPHaom5XG6zqqpH' b'crnZp3leqHE4HAJjhmlZ1nWXy91VVlb2scvlGrgdn7vTn0vX9YcUZey4YRh7/P618Pl8EEUR' b'HMeBiKDrOqan00gmUxBF5/eVleuPiqJ4ZYkQERXdUqnUgUgkrCeTSSomVFWlcDisp1LJA4u1' b'ijZNJtW2SCSsG4ZR0MRkJjHGluCGYdDg4KCuqmpbvl5RqdY0bWs0Kvdu2lTHC4Jg4xNTUzj/' b'axd6+/swFY/BAmGtrxS1VSE0N25By7btAADGGIaG/rZqamqbJEm6VOwec1evDvVVVq7fXFJS' b'YoNffHcW7536CGOxSbRs2YbWpmaACOe7L6LrUi9at27HJ+9/aM/PZrMYHR0dqK+vbwSwcooz' b'mcxBWZYXpO/zs19TbctWCrU2UcfJE2Sapj2m6Tq9cuwN2vdi2wKciOjatWuUyWQOEtHK51hV' b'1fZgMGj3R0ZvouPkCXCSiIbHn8CrbUfA8/MyotOJlw8ehkeUYJrmAq3y8nIkEvF2YOUCUqJp' b'uR35Kf7mpx+h/pOB0+3CzqYdEEVxCemRunq89drrGL45At0wbNzr9SKXy+0AULKsMWNsoyA4' b'hP9WRES4fCUMye2CJEmoqwoV5Gm6jg9On8KXP3TCMHQb53keguAQGGMbVzA2/Iyx/A9BOpuB' b'Q3SC53l4PZ7bcku8Xmzc8AAkUVqAW5YJxgz/ssYcx+s8z+X1OQiCAI7j7JUV5gH7dz2L3//8' b'A2OTE4s153SXM3Y6neP5x83hcKD8/gBMZsJgDKOT4wV5umGgq7cHD1bXYF0guGDMsiyIoqgs' b'a8zz/BgRFE3TbKxly5NghgEQoedyf0Gez7sGmVQKjQ8/Bpc0n2pNywEgheO45Y0BoLS09LNE' b'ImH3d7e04tHqWui6ju7L/egZ6FvCudjbgzPnOrG4NMXjCfj9/k8BrFyrGWOBcDiczi8GV6My' b'7W0/TKFdzdR8aD99e+E83UqnKT09Tee6LlDDvp3U+NwzpKZS87XcNGlw8K80YyxQdK2Ox+PH' b'Mpnpt6uqqm1sKhHH6a/O4OffujExOYX71qyZu8HSaZQHg3hhz/N46VCbPV+WZZSW+t4MBILv' b'AMXfx1I0Gj3n8XieqqioWDCQyWYxoowhps5tx7pAEBsq18PjdttzxscVzMzM/hIKhXYD0IpK' b'dV5zybLcfePGMFmWVfBqXByWZdHwcJRk+Xo3Ebnz9e7oIUBEHkVROiKRiBaLxQrev0REjDGK' b'xaYoEglriqK8S0SexVp3/PQBAF3X6yYnJ47PzMzsdTic/Hy9nnv6GAazPB5PZ0VFxVFRFIcK' b'aazKOC/8mqY15HKzVaZpeXmez7rdrmFJcvUDuLUc8f8arzruvQf9v42ByFQYvgT4AAAAAElF' b'TkSuQmCC') index.append('cecilia-normal-trans') catalog['cecilia-normal-trans'] = cecilia_normal_trans #---------------------------------------------------------------------- close_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAKtJREFUOI3tk70NwyAQRp+jDJAN7DT2GGkpM4I3gBE8Apsk' b'nb1Faqp4BDIBKUyFzpBIFCnySRR3fDzu+GlCCNTQoQrlD/pIxzQxmOUE3GPYAl1i8UDnrHpl' b'K3JWeeABXAQIgE0hIihqAlYh7wErLRBBccdRmDJSNQBN7mUPZrkB1xiuzqrznrd0ayNbO7C1' b'u68QQnb0eta9np8lXxEUYW3Jkz2jb/R7X6Qa6A02643KV+L/pwAAAABJRU5ErkJggg==') index.append('close-hover-trans') catalog['close-hover-trans'] = close_hover_trans #---------------------------------------------------------------------- close_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAIxJREFUOI3tk8ENwjAQBCc0YDqATlJKSqAVOqEFOqAEiwoC' b'FUw+fiDs2FaUDxIr7edub3T3uEFlDx12ofxBm0FH4J4cAb88A6EH9AIewAicC/0r8M6qaslB' b'jeaaUy+bWQOhjgXQtJavgVBvH5BYy7ZAIZ1T3aYHhHppbaMy2Pe0J+BZC/SCmvqNF9mkBXQJ' b'M9Cu1Q41AAAAAElFTkSuQmCC') index.append('close-normal-trans') catalog['close-normal-trans'] = close_normal_trans #---------------------------------------------------------------------- delete_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAANdJREFUOI3dk70NwjAUhD8QfbIBdC7JCNC5S9ggG+ARMoJH' b'YISkc0k2SOsuI8AEoeBZSJAfQCkQr7Gei+9Od/ai6zrmmOUslP8GrZ4vlHEboAEiYO+tPivj' b'YqCVu4O3upx05K1ugUJWK2chkKoPArAYql8Z1wBboAJS4AokIvQyYxnlcqbB1RBk1JG4KoMb' b'b3U8IjrsSELfyRop47KvQNyDjoA67NLe+yBRDwFnAlvzaHMaJKqh9txbfeEe/BU4KuOSdx0V' b'ol6HNyNtBfipDzTa2ifze5/2BlE8SAXEJVqGAAAAAElFTkSuQmCC') index.append('delete-hover-trans') catalog['delete-hover-trans'] = delete_hover_trans #---------------------------------------------------------------------- delete_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAJpJREFUOI3llFENhDAQRAcUVEIlIAEJlYIEJCDlJCABCScB' b'B+8+2CZ7pDQH4YubpGl2uvu6mzRtAN2h9hbK/4GipFUSknrzgvNSkQSU1sCmxeLJ4tdB/iFI' b'BsnFACsQr4A6vjVUcqsg7bqp5tYOowGy0lVQ7ma2/Q2Es6DkRgoONp0BBbvdj+PH7H4F5Tcz' b'7/zR/KUEanjsN/IBfIUTn7eS1Y8AAAAASUVORK5CYII=') index.append('delete-normal-trans') catalog['delete-normal-trans'] = delete_normal_trans #---------------------------------------------------------------------- edit_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABMAAAAUCAYAAABvVQZ0AAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAUpJREFUOI3l079L1WEUx/GXWWAIbhpCCg3yxUGQSII2DeQB' b'N4cGISVEITT44tQk2RRU9KyRoz/+AKm+SzjUoIOCJMkzqEN/QS2Cy2243wtx+d4WHYTOcnjO' b'4fPm/HraarWay7Jrl0b6f2DXmwNZXnRgFY8wk2LYrBJmeXED/ehOMey0qmwDR3iK+1leDFeA' b'OvEK+9hqxKtg99CHj7iJjibQNL5jCV8x37JNPMZo6cdxjJ0sL27hdRn/iUV8SjGcNoRtVUeb' b'5cWdsroejKELveozWsfnFMNus64S1gRexWz53MNIiqFS1KqyuxjAEG7jBIN4gG/YxJcUw9k/' b'YVleTOIhztXn8jzF8LbMLeMZOvEea/jRgFZt8w1+4QXeqW8MpBheqi9nGwv4gKlGvgp2UPon' b'pf/9dzLFcJhimMAc2rHSss2L2NX96FcX9gdUL2F//BM8JAAAAABJRU5ErkJggg==') index.append('edit-hover-trans') catalog['edit-hover-trans'] = edit_hover_trans #---------------------------------------------------------------------- edit_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABEAAAAUCAYAAABroNZJAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAASFJREFUOI3dk70rhWEchq+XlJNiI4U6yaAoIWRRVpvNoiRK' b'NmIUi2Qx2Uzy8QfoZDKxKJ0SZTvKajNQx3AZPC9vr/cYnIHc9fSr535+V/fzFalUq5qqCf8b' b'Ug8cAGVg6pueOqATGMmCHAF3wAIwDPRlABqALaAInGRBBoF2oADkQrKkpoEbYAk4B+Y/HDUe' b'Y+q6uqLeq6thvkXd910P6qKaT/QRpR5bPqRpBsaBRqAV6AAOgVPg8ssmk8TU2PNTV2pUaW06' b'ST/QBfQCbUAJ6AZGgQvgGDgDXiolmVR31R31VV1OeGvqo/oc/AE1F/tJSEndVJvUbXUoFbtH' b'LahltajOxl7yiq9DnQn1KXV8t8AEMAfUAhuxkT6TH+lvfsDfhbwBDZwChidQy5sAAAAASUVO' b'RK5CYII=') index.append('edit-normal-trans') catalog['edit-normal-trans'] = edit_normal_trans #---------------------------------------------------------------------- filer_click_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAABPBJREFUWIXNmGtoFFcYht8zOzuT3Un2kmw22WRjyCqpIdZL' b'sVkrtbRC441Sf6QttjT9YYMlARVTDJYWRERrwdpgS0MQQQTbHyoIXqLWatUmUlNtkxaTmk3N' b'msvGzd6zs5vZnTn9odVUJdnJmuoLA8Nw5puH75zvPd8ZQinFsyzmaQNMJlbtC2I8Dr/fD0yQ' b'eVlRYDKZYMzKSgsOUAnY3d2ddebQoTeCN29WaijNfuwgQjAiioGo2Xx03YYNp1+YPz+eDiBJ' b'dQ3e8fuf+7qh4aexq1cZTzR6JCCKNxiGoePflynFrJycV57Py1t9sa9v9JbZfGJnY2Pt4kWL' b'wlMFTDmDv7a1rWZ7evjLXu92t17/7fKqKlGv00FRlLsDCIFCKSxuN+UikZULios/N3u9zg1r' b'1x7ctmfPBysqK4PTCpiMxfSEUrFXFP9ev3lzfF1NDTQMg4fz37lvn3Lz+HHiDoVCvmRyUwXD' b'XNje0FBHKf1i5bJlCbWAKVfxvzCcVkuL7HYIej0yMjKge+iiihIAIfys3FxbrySNSCzbNE+W' b'126try89d+ECq9bWpmQzsiz/5/7OwAAJBwIAAM5ovJhUlD8sHFf77uzZH0UTCVep1Rp/UaM5' b'9s2OHXUjPp8w7YDj1XXtmnFXXV3FsQMHdABQvGTJgKW0dGlsdHS9WZKcK4uKNuXwfGxhQcHM' b'XEn6OOT3F6uJr9oHH9Zvra22P1tbZ8oMM1xVU3Mr2+HAyw0NPldLy3eRoaHDjEbDd3R05IZv' b'33YNJZMhGRD/V0BFkiw5BkN5wOvtDQeDt3SCAKPdjnnV1ZAlKQEgcWXXLr2vqws+WaZJRVG1' b'CKc0xYSQBwEUJWuG0fh6UhQLhwcH7z/XcBy4zExwmZkgLAsCgDwm1mRSnUFCCCilGO7vh9vl' b'gtfj4Ry5uWU0FHr7SFPTD6FAIFRSWgqdIECr1ULL8w+8croBCQANy6KrvV2ItLR85uvtFeOE' b'2AtNpkynIFQN9vXN/XHnzoM6i8XD6vUhlueTMseFOzs7B+1aLTAFUNUZZBgGnv5+OeJysVpZ' b'LpEI0cNkAgghGp43Bjyekr/6+phYMqkjhEQlQI4DGcU2m2o41YAUgJxMYu7ixXGuvPzTS2fP' b'sjylK+Ky/KbL7x+62t9fn+9wnHGuWhW12mw002CgepOJnjx82Jro6JiwA3oigMBdY87Jy8Nb' b'tbXiexs34vvdu6M9p06FOgOBc8urq8++U1MTtuTngxByv5iut7fDc/26argpAQIApRQMw0An' b'CADLxnpHRs7zZvPJ1dXVYWtBwSPj0ymStHcSotUORiSpRTAYfhcE4YmfH9IGNJjNg6FotMda' b'VDRszH58D5uOpjTF4426YunScOWaNb+UO52yhk17Y3pEKUeklIK515SOB7Q7HHTL3r0Tt/WE' b'gNyLMW2A0UQikZBlrjAjQ/tzWxuZt2ABOK12wo8SQjASDEJ0u3kAiCQSEgFUVUzKgLYZM87v' b'DwS2vFpYuNl/6VJw/40b3TzPT+ptZGyMF8bGPjk5MOA3lpWdyLdaVbX+KQNWLFx45aWqqteO' b'Nje/X6LTfcn6fCaF0gn3f0IIZEBxhUKdo4Lw4Vf19Zezs7MjagBTPtUBgBiLoam5mdu2dWtB' b'LBKx0EkaFAJAoVQumzNnoLGx0eusqFAopRCE1JtqVYBPQ8/8r49nHvAfh6kkrQ1tQ5YAAAAA' b'SUVORK5CYII=') index.append('filer-click-trans') catalog['filer-click-trans'] = filer_click_trans #---------------------------------------------------------------------- filer_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAABL5JREFUWIXNmGtsFFUUx//3zmNnt93OLm2hpYU+kJeFgkpK' b'VEJArcGWgEZFQgRFISamRhMlPhGNhhgaozEhwUTE2g8SNVZAHqEYrJaHEltoKba0YFvaLXTb' b'fXWnuzududcPqJTQpDvbovyT+TC5j/Obc+8959whnHPcyqL/N8BoEhMZxBgD4wAZoY0DECgB' b'ISO1WpclQMMwlV+b/iyta+54MBqNuUaCYIwj1ZXsXVg47fM5+Vl1YwUkFvZg/p6f6mu3fFrF' b'Ojz9JwzGmulVh10ngZL5oiQumZGT0fv6+tI9xQsLNnEOJktCQl6NG9Af1F58+p2dm4+dOV85' b'b/rU94vvLuhPUmSwYeNlUUBDa9czv5xufWMgEp1HQba/9lSJ8Xhx0XPuFIeZCGDcSxyOxOyh' b'cER3O5OaylY/4CtdVAiB0utcSAnBt0dOsa7egPjo/Qu0rTv3lZVXHDotS+KatcvvrRQF64Dx' b'n2ICEAI47DaWl5UOWRIhCBTisIdSgmSH0u8LhicXFeRml7/0RFiyieVbd+3ftGtvrWKa7CYC' b'/iPOYZrmv6+mydDtDRB/SAMA3DkrpyZ3cmrtlh1V1frQ0JrC27JaAwOa/mHlwZPfVJ8qMUzT' b'ks0xx8H6lk71hW27i77cf9wOABlpamjzxpX3pbmdqyp+ODaVMV4yf+bU41pEL/xiX215IKTl' b'WJk/oTg4XMcaL2YeP9cxjRLzyoaHF7cn2W2YlZuJ7a+ua+wLhhsJgIvd3uTnt1aUmYzTIYNZ' b'sjlmDw4ZLC0lRS3wBrSMwMDgtYkpwUS3E+luJ9xOBwglIBSWQ01CgMONMBCnmp5ZrOksy+P1' b'jdjfZByJpnzLS0wIAM7R7unDpZ4+XO4LymmTpswOgq/a8V3tEX9oMDgzJwMORYYkClBsMhhj' b'SDTzWQMkgCQKqGvpdPzcOrClyxcbJGY025XtTE5KmftYp7+v8IPvz1amOc9ftoskqEjUUESE' b'An5fWzAcQbrLeWPqGVdAAJRQdPf6zXNtHpHIjjzCdIeLA4SCKCJVe4KhvM6OADX0mJ0SaGCG' b'CRZTDH0IhJARC4zxA+SAYTIsnJMfA/hbNb83i1ywPWTo0ZUB76UeT1vTy/mZrsPLl07XMlJV' b'nuJwcLeaxH0hLXlbxUGYzHqgtuxBwzQxaUIK3nx2xeAr65bho9012oGGzqC/u+3HtcsWVG94' b'ZGkoI00FwbXD1NLeA1Gglpc3IUAAYJz/nd4UCASR/svdR1126cCTJYtCk9NdN/Q3WeJV+5jj' b'oCwQjx4NH1Id8hmnwzbu94cxA7qddo8W1tqyJ7mvuNXk8WC6TmNOdUsWzAqtLr7rt6Lb80xR' b'GP8rTtyABFfLZ0IIBHoNJD8rnX+yaU10tMGc8xHq79EV9ycrNjlmt0mSbpiIRHVLRhrPX1IC' b'A4Pg4IZVzLg9OEFNOnzH7Nx365rbN3z8VXX9ybMXukZzCiUEV3xB29FTf2weGIz2ZqaqDS6n' b'PXhTACkhTWtL75lX39xeVlPX8vXJxjY1nrxgMsZMxtqnTZm4ff3KxVV2m9xnBdDKrQ4AcLHL' b'S977bO+Mwyca58qSNOoW0Q2Dz5+Zc+HtjSsaigryDKvllmXA/1q3/K+PWx7wLy0N8w8GixGz' b'AAAAAElFTkSuQmCC') index.append('filer-hover-trans') catalog['filer-hover-trans'] = filer_hover_trans #---------------------------------------------------------------------- filer_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAABORJREFUWIXNmF9IW1ccx3/n3Jvce3OtpiP4Z5nVVlkQqQXF' b'KaVVNuiqo6IPsoeVISXQTktH6XSyEemLUGvpk223SqGUgD4prtDh1NFNhaClhXSry4LR2LRG' b'm94Yo8Y095579tCZNdtsE6u2X7hw7uEc+HB+53x/v3MQpRTeZuE3DfAqsYlOWF5eBr/fDxhj' b'WG/1FUUBvV4PO3fu3F5Ah8Ox4969e1Ucx33Mcdw76wHOzMwsOByO3rq6up+Ki4vDrwOI4t2D' b'c3Nzpr6+vl9NJhN2uVw9s7OzfzAMEzNZlmUoLi4u279/f01XV9fyzZs3b1ksloaDBw8GNwoY' b'9wrev3+/Ji8vj+vu7m4dGBj47vDhwyGdThcNM6UUFEUBSikVRfGTioqKNqPRWNLS0mJtbGys' b'O3LkSGBLATHGOp7nQ2NjY9MNDQ3h+vp6wPifM0YpBYQQaLVaFSGE7Hb7otvtPnPixIlfOjo6' b'TlJK26uqquREAeM+xRhjIISARqOhmZmZIIoiCIIQ/XQ6HQiCAAzDLGCMucLCwoyRkZGnwWDw' b'+6NHj5ovXLjw/tDQEJuorSVsMwghIIRE/xVFAY/HgyRJWusaBoDfs7OzG9ra2r4IBAKu0tLS' b'cH19/Q83btw4KUmSuKWA/9bY2FjK8ePHP7hy5Yrwd9djAPgIAL40mUwlTU1NZ/bs2bNaXV2d' b'U1RU1BgMBrO2FXBkZCTDbrfnDA4Opi0tLa11SwDQDQCfYYwPTUxM1DqdTjoxMbEYiURC2wqI' b'EDLk5ubmS5KU/kKY1yQTQpZtNtuKx+MBt9tNZVlOaBNuCBAhFG1zHLejoqLiEMbYODk5+Z+x' b'DMNE56y1E1HCqQ4hBJRScLlc4HA44NGjR9pjx47lra6ufnr16tWhhYWFxfz8fEhKSgKO44Dn' b'eVBVNWGwDQEihIBlWbhz545ICGnBGIcMBsN7OTk5SWfPnq0dHx8vsNls1unp6TlZlhdZllUk' b'SQpOTU3Nms3mmJXfEkCA5344NTVFQqEQm5WVtdvn8+kIIcCyLFJVNeXhw4e7nU4n9vl8wrNn' b'z1bm5+dJWloav5HwJgxIKQVVVaGsrCzs9Xot169fZzMyMir9fn91T0+P99y5c1+lpqYOlJeX' b'r5SXl9Pk5GQqiiJ98OBBKiFk3epn0wABnhcERqMRTp8+HbJYLNDZ2bnS29u7eOnSpZ9ra2sH' b'm5ubg0ajETDG0ZBeu3Ytxty3FBAAQFVVYBgG9Ho9KIqy2t/ffxsAfjx16lRw165dMWMJIRuG' b'A9gEH1QUZdbtdvcLgmBPSUnZ9PvDawMmJyfPer3eyezs7HmDwbAZTDHaUIhftIvKysrg3bt3' b'xw8cOEA0Gs2mga0pbkBKKTAMA4qixACaTCZqtVrXLesZhgFKaUztuCWAgUBARghpCwoKNMPD' b'w2jv3r2g1Wqjher/WQhCCJ48eQI8z3MajQZ8Pl+EYZiE0krcgAaD4bbVav3GbDZ/PTMzE7Db' b'7X/yPP9Sb6OUAsdx3L59+75tb2/38zx/Kz09PaHSP+5LUzgcZs6fP180Ojr6eUlJyYeCIOhV' b'VX1p7kIIgSzLqs1m+83lcnVevHhxtKam5umWAAIAhEIhuHz5sra1tfXdSCRiAIBXJldVVUlu' b'bu7jjo4OX2lpqUopBVGMv6hOCPBN6K1/+njrAf8CvsEoAJzTrm8AAAAASUVORK5CYII=') index.append('filer-normal-trans') catalog['filer-normal-trans'] = filer_normal_trans #---------------------------------------------------------------------- Grapher_background = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAACcQAAAV4CAIAAAALudsQAAAAA3NCSVQICAjb4U/gAAAACXBI' b'WXMAAArwAAAK8AFCrDSYAAAgAElEQVR4nOzdoRHDMBBFwbNrUP8VqQgV4CICDPN4AnbRg+Ka' b'uX+dc2bmeZ6ZWWtprbXWWmuttdZaa6211lprrbWemXsAAAAAAAAA+OIzFQAAAAAAACBce+9f' b'vwEAAAAAAADg79xrrffs78xorbXWWmuttdZaa6211lprrbV+25lfAAAAAAAAgOAzFQAAAAAA' b'ACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhM' b'BQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAA' b'AAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAI' b'NlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XW' b'WmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa621' b'1lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmut' b'tdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lpr' b'rbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY6' b'2plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8A' b'AAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAA' b'AIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAz' b'FQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAA' b'AAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg' b'2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUA' b'AAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAA' b'AAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZT' b'tdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lpr' b'rbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZa' b'a6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXW' b'WmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa621' b'1jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZ' b'XwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAA' b'AAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA' b'4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUA' b'AAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAA' b'ACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhM' b'BQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAA' b'AAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAI' b'NlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XW' b'WmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa621' b'1lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmut' b'tdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lpr' b'rbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY6' b'2plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8A' b'AAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAA' b'AIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAz' b'FQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAA' b'AAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg' b'2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUA' b'AAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAA' b'AAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZT' b'tdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lpr' b'rbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZa' b'a6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXW' b'WmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa621' b'1jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZ' b'XwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAA' b'AAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA' b'4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUA' b'AAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAA' b'ACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhM' b'BQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAA' b'AAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAI' b'NlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XW' b'WmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa621' b'1lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmut' b'tdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lpr' b'rbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY6' b'2plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8A' b'AAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAA' b'AIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAz' b'FQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAA' b'AAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg' b'2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUA' b'AAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAA' b'AAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZT' b'tdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lpr' b'rbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZa' b'a6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXW' b'WmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa621' b'1jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZ' b'XwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAA' b'AAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA' b'4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUA' b'AAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAA' b'ACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhM' b'BQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAA' b'AAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAI' b'NlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XW' b'WmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa621' b'1lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmut' b'tdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lpr' b'rbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY6' b'2plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8A' b'AAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAA' b'AIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAz' b'FQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAA' b'AAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg' b'2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUA' b'AAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAA' b'AAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZT' b'tdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lpr' b'rbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZa' b'a6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXW' b'WmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa621' b'1jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZ' b'XwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAA' b'AAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA' b'4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUA' b'AAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAA' b'ACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhM' b'BQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAA' b'AAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAI' b'NlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XW' b'WmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa621' b'1lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmut' b'tdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lpr' b'rbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY6' b'2plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8A' b'AAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAA' b'AIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAz' b'FQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAA' b'AAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg' b'2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUA' b'AAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAA' b'AAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZT' b'tdZaa6211lprrbXWWmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lpr' b'rbXWWmuttdZaa6211jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZa' b'a6211lprrbXWOtqZXwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXW' b'WmuttdY62plfAAAAAAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa621' b'1jramV8AAAAAAACA4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZ' b'XwAAAAAAAIDgMxUAAAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62plfAAAA' b'AAAAgOAzFQAAAAAAACDYTAUAAAAAAAAINlO11lprrbXWWmuttdZaa6211jramV8AAAAAAACA' b'4DMVAAAAAAAAINhMBQAAAAAAAAg2U7XWWmuttdZaa6211lprrbXWOtqZXwAAAAAAAIDgMxUA' b'AAAAAAAg2EwFAAAAAAAACDZTtdZaa6211lprrbXWWmuttdY62pnfD3t3UAMAAAMhzL9rDCCh' b'feFhyQ4AAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vz' b'CwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAA' b'AAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAM' b'x1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAA' b'AAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAA' b'YNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYC' b'AAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAA' b'AACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNm' b'qtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa621' b'1lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXW' b'WmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZa' b'a6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lpr' b'rfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqb' b'XwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAA' b'AAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABg' b'OKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEA' b'AAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAA' b'AMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMV' b'AAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAA' b'AAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2' b'U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmut' b'tdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa621' b'1lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXW' b'WmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZa' b'a62nvfkFAFJ7iq4AACAASURBVAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXW' b'WmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZa' b'a6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lpr' b'rfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqb' b'XwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAA' b'AAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABg' b'OKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEA' b'AAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAA' b'AMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMV' b'AAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAA' b'AAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2' b'U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmut' b'tdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa621' b'1lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXW' b'WmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZa' b'a62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe' b'/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAA' b'AAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAA' b'wzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoA' b'AAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAA' b'ABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJup' b'AAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAA' b'AACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCw' b'maq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1Vpr' b'rbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmut' b'tdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa621' b'1lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXW' b'Wms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ72' b'5hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAA' b'AAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAA' b'GI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QA' b'AAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAA' b'AMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhM' b'BQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAA' b'AAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACG' b'zVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZa' b'a6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lpr' b'rbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmut' b'tdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa621' b'1lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0' b'N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAA' b'AAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAA' b'wHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYC' b'AAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAA' b'AACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNm' b'KgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAA' b'AAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAw' b'bKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XW' b'WmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZa' b'a6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lpr' b'rbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmut' b'tdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62n' b'vfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIA' b'AAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAA' b'AIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEV' b'AAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAA' b'AAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2' b'UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAA' b'AAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACA' b'YTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq1' b'1lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXW' b'WmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZa' b'a6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lpr' b'rbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms9' b'7c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcA' b'AAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAA' b'ADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6p' b'AAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAA' b'AACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCw' b'mQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAA' b'AAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAA' b'DJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSt' b'tdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa621' b'1lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXW' b'WmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZa' b'a6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrr' b'aW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78A' b'AAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAA' b'AIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBM' b'BQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAA' b'AAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACG' b'zVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAA' b'AAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAA' b'YNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZq' b'rbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmut' b'tdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa621' b'1lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXW' b'WmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZa' b'T3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkF' b'AAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAA' b'AAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZj' b'KgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAA' b'AAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAw' b'bKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEA' b'AAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAA' b'AMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNV' b'a6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lpr' b'rbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmut' b'tdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa621' b'1lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXW' b'etqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0v' b'AAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAA' b'AABgOKYCAAAAAAAADJupQO3dQQ0AAAyEMP+uMYCE9oWHJTsAAAAAAACGzVSttdZaa6211lpr' b'rbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmut' b'tdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62n' b'vfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIA' b'AAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAA' b'AIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEV' b'AAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAA' b'AAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2' b'UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAA' b'AAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACA' b'YTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq1' b'1lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXW' b'WmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZa' b'a6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lpr' b'rbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms9' b'7c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcA' b'AAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAA' b'ADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6p' b'AAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAA' b'AACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCw' b'mQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAA' b'AAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAA' b'DJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSt' b'tdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa621' b'1lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXW' b'WmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZa' b'a6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrr' b'aW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78A' b'AAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAA' b'AIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBM' b'BQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAA' b'AAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACG' b'zVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAA' b'AAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAA' b'YNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZq' b'rbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmut' b'tdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa621' b'1lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXW' b'WmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZa' b'T3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkF' b'AAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAA' b'AAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZj' b'KgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAA' b'AAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAw' b'bKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEA' b'AAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAA' b'AMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNV' b'a6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lpr' b'rbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmut' b'tdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa621' b'1lprZX1MOAAABKBJREFUrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmut' b'tdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa621' b'1lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXW' b'WmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZa' b'T3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkF' b'AAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAA' b'AAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZj' b'KgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa6211lprrfW0N78AAAAAAAAAwzEVAAAA' b'AAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXWetqbXwAAAAAAAIDhmAoAAAAAAAAw' b'bKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0vAAAAAAAAwHBMBQAAAAAAABg2UwEA' b'AAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAAAABgOKYCAAAAAAAADJupAAAAAAAA' b'AMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAcUwEAAAAAAACGzVQAAAAAAACAYTNV' b'a6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAAAAAAAMNmKgAAAAAAAMCwmaq11lpr' b'rbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACAYTMVAAAAAAAAYNhM1VprrbXWWmut' b'tdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoAAAAAAAAwbKZqrbXWWmuttdZaa621' b'1lprrfW0N78AAAAAAAAAwzEVAAAAAAAAYNhMBQAAAAAAABg2U7XWWmuttdZaa6211lprrbXW' b'etqbXwAAAAAAAIDhmAoAAAAAAAAwbKYCAAAAAAAADJupWmuttdZaa6211lprrbXWWms97c0v' b'AAAAAAAAwHBMBQAAAAAAABg2UwEAAAAAAACGzVSttdZaa6211lprrbXWWmuttZ725hcAAAAA' b'AABgOKYCAAAAAAAADJupAAAAAAAAAMNmqtZaa6211lprrbXWWmuttdZaT3vzCwAAAAAAADAc' b'UwEAAAAAAACGzVQAAAAAAACAYTNVa6211lprrbXWWmuttdZaa62nvfkFAAAAAAAAGI6pAAAA' b'AAAAAMNmKgAAAAAAAMCwmaq11lprrbXWWmuttdZaa6211tPe/AIAAAAAAAAMx1QAAAAAAACA' b'YTMVAAAAAAAAYNhM1VprrbXWWmuttdZaa6211lrraW9+AQAAAAAAAIZjKgAAAAAAAMCwmQoA' b'AAAAAAAwApM9IcxQ55JxAAAAAElFTkSuQmCC') index.append('Grapher_background') catalog['Grapher_background'] = Grapher_background #---------------------------------------------------------------------- hand_click_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAaRJREFUOI3FlL9LHUEQxz/PBCIWQopgypupLAQlJCEWIaVr' b'Y+GLFkpEDbG0tQqxVPwLUgQrf3SChTeBQLCxMlUsrHbTSCxUUBsTCGeRFd47705FwYFlh+/M' b'fHdmdndqWZZxl9Jyp2zXJTTRAxO9Vim1opJNtBtYAuqAAv3ANtAB/AGmgQ8u+O/52IclB60D' b'q8Ac8BR464Lfi1nuRsJ54GU+8FLJJtoOnLngZ4Bx4BXQ1uBSd8FvAGcm+u1KQhf8CfDTREdc' b'8McRHm5wucBeA1/zvS27lC+Ai/oa0Bv1PWDfRFvi4Qv5wKYemugs8Ax4HEnGXPCDJtoVXXpc' b'8P9MtLMh7GNVhp9c8APAO+CBiS7HTHbifhD9PPA86n1VhBMmuuyC/xUz7c6XFIn/uuB/mGgP' b'cNhkzLKsaaWJHKaJTOXxopUmspImMtSIFV1KF/DZRHsLbHl5A1hVybjgfwMvgC0TbS1jMtH3' b'wK4L/rSSMJJuAwvAZkV2o8DiJfSKHp2miTwqse0U4WV/+UImgU0TnQOeADXgiP9PpnD6FE6b' b'28j9DNibyDmbwuTQD6W51QAAAABJRU5ErkJggg==') index.append('hand-click-trans') catalog['hand-click-trans'] = hand_click_trans #---------------------------------------------------------------------- hand_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAadJREFUOI3FlDFIHEEUhr+7BAwp0kmsLXYawUOixEJSOjYW' b'uZjCEDAJpsy+ThHElIrNtCnEKme6gEX2BQJyjZWpYuE0IY0kxZ2Q2GggbApHONfd9SQH+WHY' b'x//e++e9mZ1XSdOUXqLaU7VuBY1oy4h21Uolr2UjOgy8BerAIDAF7AF3gVPgFTDvnd3J5t4s' b'2GgbeAesAgPAI+/sYajyIAiuAWPZxEstG9E7wIl3dgGYA+4DtztC6t7ZD8CJEf10paB39hfw' b'xYjOemd/BvpxR8g5NwF8zJ5t0aVsADbY74HxYB8CP4xoNWy+XlqhEX1tRLeBJWA2JD0EFkNI' b'zTv7B4g60pbLKlzxzk4DT4EbRrQRRPfDtxXivgL3gj1ZJvjMiDa8s9+AEWA421IQ/u2d/WxE' b'a0D7gjNN0wsripN2FCcvs3zeiuJkK4qTmU4u71KGgDdGdDzHl8UDQMtaxjv7HRgFdo3orSIl' b'I/oCOPDOHpcKBtE9YB1ollT3BNi8xF5xRsdRnPQV+Pbz+KK3fI7nQNOIrgL9QAU44uyXyZ0+' b'udPmX/B/Bux18Bexd+cVCoPFZQAAAABJRU5ErkJggg==') index.append('hand-hover-trans') catalog['hand-hover-trans'] = hand_hover_trans #---------------------------------------------------------------------- hand_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAUBJREFUOI3FlM0rRGEUh58ZwspslLCRUpJCbCR/wNgqC2Ul' b'lsrS2gZ/hhp2RDZKo1iI7HxlJSW7WWCl1GPzTt1m7sfQlFOnc/qde573nvd2T06lmZZvKu0X' b'QINnWi5h5FGgBMwBA0ARuAG6gS9gFVgGzuqP1jh/UbfUA/VS7Qu66qM6q17H9cbBOtWnkBcC' b'ZDACHA75uXpa2x93hx/ALbAAvAdtPlKvajPASd3dJoxcVHdCvq8eh/xVbVHz4W2JxNiRS+qR' b'eqF+R/SRELtCHIqADtOA1Yf6Q76bMEGbOhHyShpwIwIZV+8TgFUfUx+yvnJFXckAVX1PXcsC' b'9oRxpxoAvqm9WUDUyQDtSIEtqeVaPe30bfUqpV5WF38DRP1U2xNqd3F6a8byWAeegU2gEPmT' b'poHeuIakbfNn+7cF27D9AEgV0o4edBUQAAAAAElFTkSuQmCC') index.append('hand-normal-trans') catalog['hand-normal-trans'] = hand_normal_trans #---------------------------------------------------------------------- hide_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAPNJREFUOI3d0z9KQ0EQx/FPxEsIdgk+sEyffgsPkFwgqcQX' b'iKcwkBWr5ALxABavT59SWEk6wWPE5j3IXxF9hTjNwP7m9x12Zrex2WzUEWe1UP436PyUkOXF' b'0S2kGBrHzhv7W8vyYoEmLtDBupSaWOAD6xRD5yQoy4sHjPCI9xTDeK/JCJe4wzjFcH8AyvKi' b'jylmKYbBqSuXtVP0MUgxzNgd9lOZb7+C7NVUnh3QdZlfvwGqairPwYy6mGOJtxRDL8uLNqQY' b'lllezHGFNnophuejoBI2QQs3GGJVSi1M8IJVimG47TsAbQF/945+Gn/vr9UG+gThrlgHjd+z' b'/gAAAABJRU5ErkJggg==') index.append('hide-hover-trans') catalog['hide-hover-trans'] = hide_hover_trans #---------------------------------------------------------------------- hide_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAMpJREFUOI3dkzEOgkAQRR/GyB2srShtrPAcnMATeAsaWk6g' b'B/AEWlnQWllzB2zGwi+ZbBaMCYXxJxPIn7fDZHZIzIwpNJukyn8Xmo/khm4hiZmxji5Aq/cc' b'WCpyea2Y4LNmPkp7qTKzfZBDXiWm9DkP7QTUkQJh1GJ3by9xC9kBCyAFHiOzQ1wnLg1nlOl5' b'+1DEM1nvBC0Xarkxs4O8tQJ5jZjCnw2v/whsgBVQAFfgrtxW3gk4i+3lZxTqqz0aW8jogSH9' b'3r82WaEnTyLLPmnrEUcAAAAASUVORK5CYII=') index.append('hide-normal-trans') catalog['hide-normal-trans'] = hide_normal_trans #---------------------------------------------------------------------- input_1_file = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAX1JREFUOI3F1L1qVFEUBeBvz4SAhWgQkaksY5NSEQsRsdRC' b'W9HW0kIs8g5WIljE1Da+QgQHfAXLCAqKMRo0qAk6WRZ3lDG5VyekcMGBc/fPunvtc/bhfyPJ' b'X/21O7iqJLmEWxgh47gZ3K+qYZKqqlbm3h/sDdlp3MMXfMYmPuIUniS5XFX5V6WTVX5LcrPF' b'vpRkMcmrJFe78nsttj42WuxDDHAdi0luJJmZpsKtJNfG+0py5Je8JFeSrCRZTYNj+yU8mmQh' b'SW/8LclcknNJ3iU5Po3kSZzAWczSHFpVbeATdtoSunrQT3KYzFN3sJbkOb7ih6bPrWgjDE7i' b'ITXAPJbxFC/wBlv7qbCwhiWcwUWs4BHeau7nQEe7uiRvVtWzJNvkMXW7qt7/lpAc6sjrPJTx' b'SOY1tayZlBb/dIQ9fB/nrWPV3n6NuojbJI8wB1W1jZctMbPjtWeg2wjPYynJBc0V2Y0dLOAu' b'Pux2Tvt8TaKPYVU9aPnZwTD1E3YQ/ATpD8mE0kfUZwAAAABJRU5ErkJggg==') index.append('input-1-file') catalog['input-1-file'] = input_1_file #---------------------------------------------------------------------- input_2_live = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAaFJREFUOI3tkDFvE0EQhd+ak+hoQEgULihcpLKo8y+Q6FLD' b'f4AWpYj4DdQnuUiDKJCMLBdQoEhusEVcIBp8J3zXeG9v53ZmhyIcWDiJbWgoeNXuzLxPbwb4' b'r3VZa6/976wsywAAqmqccykRuaqqDv8KCgBN07zQH2Lmj3Vd3/lzGgAiOm+BMUYVkYNtnuS6' b'pqpS+zbGRGOMbgN2fi9Mp1NMJpO2btZnVVXG4/GN2Wy24bsyYbfbvQngcZ7nMwB+rVUR0b1+' b'v3/U6XReAfiwLS0AQFVvxRg/13V9QkRn+ksr59wjEVFVfXKVfyN60zQaY3RJknwzxrxRvThb' b'jPG8qqpPxhiIyE7hAADL5TIhomMRyReLxX3v/bMQwsuiKA6YOY0xFmVZ9nYnArDW3mXm18xc' b'hRBOmqZ5zsxnIlJ47x8CADPvBmtXdM7dDiEcMfM7EXEhhGMietDOzOfzS/0bNxwMBgCAuq5N' b'WZZvmXmoql9Xq9WptbYEgNFohF5vv61hrX0aQvgiIpmq5sycee/f70cBkKbpz7Uvk6piOBzu' b'zf139B3UYzEy01Hv0QAAAABJRU5ErkJggg==') index.append('input-2-live') catalog['input-2-live'] = input_2_live #---------------------------------------------------------------------- input_3_mic = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAdZJREFUOI3tk7GKU0EUhv+5ernsRgRFEpAQELcRLASxTO8T' b'WO8TWPgEClpa+Ah2IYVsE1AkxkKIhiBWCSGwsViXG8K9psjNTObMmWNhsmSvkc2KhYV/N/xn' b'vvMfzgzwX39NcRwDALTW17TWn7XW/SzLigAwm83+HExEr2Qpa+2bvC8i28Pa7faOtXaxAnrv' b'rTFmdwN0V0QeiEiU94JcoQJgVmellI6i03dE5AqAIwD7AC5sBPZ6PdXtdoPpdMq5JoG1lhuN' b'RtDv99WyyXcA9wD0Aag88CIAVCqV6977/VKp9FpEZgAuLf2ZMeZ2tVq9r5R6uUwGADv56U4B' b'C4VCWUSeeO9HQRD49YIwDG9FUfQ4CIK3a8DfKgAAa60TEROG4RGA9yvTOffJWvtVKeWYOX/v' b'l4WcKI7jy0T0jpm/DAaD4mKxeG6tfTEajYrM/NE59yFJkqvAFs/GmJ9LzbLsDjP3nXNjInpG' b'RE+Z+RszD7XWd5eJz5oY6HQ6J12NMTeI6CEzj5xzh865R1rrm6tkw+HwbCAA1Ot1AECSJOU0' b'TfeI6MBaezCZTPbSNC0DQKvV2g62rvl8XieiY+/92Hs/JqJjY0z93KBarbb6JRslIqrZbJ4/' b'4T+pH1BnLU0zFEo/AAAAAElFTkSuQmCC') index.append('input-3-mic') catalog['input-3-mic'] = input_3_mic #---------------------------------------------------------------------- input_4_mic_recirc = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAA7BJREFUOI11VE1oXFUY/V4nVuiEqSZGyEhciVjrRiwSFaVg' b'mkUjKOhSd3Gj4Erwh1YEBUGE+EeysBvjopsu3IiUNhEak2YwExJJJmSSCZOZyUySeTNv3nv1' b'vXd/vntcdF6ZaZqzu98953zn3u9yiR6AxcXFrvXs7KwVBIE1NjZmxTUAND4+fkRr3V9oNpvU' b'19dHRET1ev0ZpdTTyWTyqWQyaSqVihkaGro5MzNTGR0d9R4UpgvZbJaIiIrF4mnHcX6WUua1' b'1mBmGGMgpQQzlz3Pu1Gv188REW1tbR0J1YWVlZVXwzAsAYBSCrZt/9FoNN4HcLZarV7SWi8Y' b'Y8DMbqFQ+KEdoNtkYWGBiIiGh4ctIcQ2ADQajX8KhcJz7fuygiB4NOa7rvupMcaWUqJUKn1+' b'bDrbtq8CgOu6fy8tLQ0SEUkpT0gpP5RSIoqi12KuUuoFZv4PgG3b9vARs2Kx+KKU0gVQz+fz' b'A+1kJKVMaa03AICZb8V8AJ8dHBz8yswIguArY8wlAI/cM6zVaj8aY9BqtX6PzYiIwjA8rZQq' b'4i4ynSGMMTVjDADA87zvc7ncSero+BMAAeB8XGu1Wid9339MKbXTNrxNROQ4Tk9bM26MYSHE' b'TDab7es6MoABAGcA9MY1IcSXrut+opRaaRte933/4yiK3o051Wr17dXV1SeJiMrl8t2HzcyU' b'SCQ6zS3LssDMGWNMnoj6enp6LjLzFSLqTyQSFcuyPqrVatbg4CBiXSaTIUqn051GOQDr8ToM' b'ww+YeTcIgotKqV983x9m5loURW/GnN3d3bHNzc1/5+bmXu488alms/lXfMHGmD/joSilrjLz' b'tpTyC2a+I6X8tlNo23ZZCIFyufxWZ7LX9/f3L0spm0KIoNls/tY5HKXUZWb2oyh6p80nIqL1' b'9fUzQggwc9bzvDR1bhIRaa2njDFwXfe7uOZ53gWl1C1mdoUQ13zff56IaHJystdxnHkAcBzn' b'SteE5+fniYhoYmIiFQQBlFLY2dn5pp3upFLqcQADUsoBAInp6elUo9G4zswAUFxbWxu6PxxN' b'TU3FiZ8FcEcpBQBfVyqV8wBOAUgB6K3X629ora8xM4QQ0d7e3ntERKVSiY4g7hAEwUtCiCWt' b'NaSUMMbkAeSNMdtKKWitEYbhzcPDw3NERAcHB/e+sGP/so2NjSfS6fQrAC6kUqmzlmWxMeaE' b'67oZIrqxvLx8e2RkpNVoNKi/v/84m/bj7EAul3tYa90LICmlTGYymYeOVxP9D9eYAXHWaByG' b'AAAAAElFTkSuQmCC') index.append('input-4-mic-recirc') catalog['input-4-mic-recirc'] = input_4_mic_recirc #---------------------------------------------------------------------- knob_disab_sm = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAACHVJREFUWIXNmF1oHNcVx393ZnZ2V155vbuWV/JHFIGsVNgE' b'Q0MDFpgQtxBoCa7dgChpEhrSUGhIoS15SNO0zYMfamOSvrWlcZM0dkNpFUFIHduidoQxcqza' b'hbW1+haSN5UiRbvS2tr5vH3QzGo0kh2llqEHDju7O3PnN/97z51zjpBS8v9synoP+NBDD726' b'nuOtCVAsmSKE2CuE+KYQQhdCRIUQcSFEjecbpJT7hRAJIcSGwO9x71xdCBERQqjeWEIIIe4K' b'0BtAeOeqQA3wDBABdM+jQMxzK/TdP+4ADgKa56o35h0htTWA+e4P+C/AAR4BegK/K4BwHGcE' b'2ABIz12gBWgCTnsP5Hjuep9SCCEBKUNBIVYLkhCcr5waePq2w4cP721razsohNjpui6O41Td' b'/+66LqZpDh47dqzvwoULvcDvAdtT2fY8CLsCcgVgCG4f8BXgHQ9MP3v27FPJZPIH0Wi02XVd' b'bNvGsixs28ZxHGzbXgZo2za2bVMul4fGx8ePv/zyy+8CpgdprgK6DHI1QCWg3APAq8C/T58+' b'XdmyZctz8Xh8pxACx3EwDIOBgQEKhQKzs7NYloUQAl3X2bRpE+l0mkQigWVZWJZFpVKhXC4P' b'jY2NHT9y5Mg73qy8AxwCZj1Q1wN0VwB66vnrSWMxEOqz2eypDz744P5MJgNAsVjk/PnzjIyM' b'YFkWtbW1pFIpamtrURSFcrlMqVSiWCwSjUZpbGwkmUxiGAaGYWDbNl1dXa93dnZ+FfgH8Adg' b'ITD1LuBKKaUWggsGhAZEurq6Hq+vr78/Fovhui49PT10d3eTSCTYvXs3dXV1RKNRVFUlEomg' b'qiqapqFpGkIIxsbGuHLlCoODg7S2tmLbNqZp0tDQ8GJra2vn9evXTwbEkAEXQoglBQPq+YGg' b'f/TRR9/PZrNH4vG4/9Tk83mam5tpaGhA1/VlYGHAIOjg4CDnzp2jqamJcrmMZVm4rks+n//l' b'yZMn3/YUrITWpRvcB8PTqyeTyec1TcOyLM6ePcv169fZvXs32WyWL9hfl5mqquzatYtDhw7R' b'399PTU1NNbh27NjxNEv7pe7du7pthQG/C7wHtL/55pvP67q+03VdLl26RD6fp7W1lQ0bNqwZ' b'LGzbtm2jvb2diYkJ0uk0juOgqmrTgQMH2oGkd//fEtjEw4AdwBvA1y5evPiCEIJSqURPTw/b' b't28nkUj8z3C+NTY2sm/fPsbGxohEIliWRUNDw1PAKWAv8OfbASoszvvlzs7OvmeffXaz67pc' b'uHCBWCxGOp2+azjf9uzZU418x3GIx+ON+/fvfwv4OZALACqrrUG1pqbmOSklpmkyPj5ONptF' b'UdYv8YlEIrS1tTE1NYWUEtu22bVr13dYekffVkEFUIUQO23bpr+/H9u22bhx47rB+bZnzx7K' b'5TLZbBbHcdB1vYmVicSqCir+K2tiYoJ4PP6lInatpqoqmUwGXdeRUuI4DixPPBRAVDfqlpaW' b'bZZlxS3LqrEsC1VVKZVKxGKxdYfzraGhgdnZ2WqSkUqltui6vjESiVQWFhbGZ2ZmjCpgIpH4' b'UyqVuk9KqRUKBRobG7FtG1VV7xlgKpViamoK27bRNI2WlpZfbNq06T7HcaxCofCzmZmZD6uA' b'vb2933jllVe+bhhGbV1d3due5NzLmsUf28+EyuXyrx5++OEmRVGmzpw5cw5CCetrr73WDdR0' b'dHT4m+g9BZyZmUHTtOoU53K5Yi6XOweU/XNW3Tv8CxKJBJVK5Z4BTk5OoqpqVUGWEoWqBQGr' b'mYQfxZlMBsMwcF133eFM02RmZoZisYhpmmHAqocVlID7+eefD1cqFVKpFJqmMTc3t+6AuVyO' b'2tpacrkclmUxMTFRYLEgc1lK/1co+ADw05deeik6OjqKYRik02kmJyfXVUXTNKs55cLCAl5C' b'UgCOAT9ksehyCSnoAt8GSqVS6UAqlRqqVCqk02kMw2B6enrdAC9fvszc3ByXLl3y65mxubm5' b'F4CfsJQ0u4TyQQn8msV05z9DQ0PHDcPANE3q6+u5ceMG8/Pzdw03PDxMd3c3mqbx2Wef+Vn3' b'eyym+wXgKDDNbRT061T76NGj75bL5SHDMABIp9Ncu3btrtbjyMgIJ06cIJFIcP78eWzbBhjr' b'6+v7G0uVnl/hraqgD2gB5vDw8HGvtkVKSSaT4dq1a9y4ccOPujWZaZpcvHiR999/n82bN3Pq' b'1Klq8fTpp5/+xYPz3QoAymBN4hfoGoupdwyIP/rooz/esWPHi9lsFikl0WiU2dlZYrEYzc3N' b'bN269bZFEyxO6dWrV7l58yZCCD7++GNM0yQej9Pb21uwbfuPwAngFivrEueORROwFTjc3Nws' b'n3jiiccNw8BxHBRFIR6Pc/PmTQzDIJFIkEwmSSaTKIrC/Pw8xWKRubk5ampqiMfjfPLJJ0xP' b'T/sJKoVC4Tejo6P/BF4Aulisj1cUTWHAIGQEOMNi3frWwYMHn9y+ffvTiqI0WZaF4zhIKUkm' b'k0QiEVzXRUqJlLJayZVKJfL5PAsLC9i2jaIoCCHGJicnTw4MDPzVA8oABjAQWofLpzikog+Z' b'8taD5k159LHHHmtvamp6UgjR5Lc5/FZHsP3h17+WZSGlxHXdsYmJiff6+/v/7gFVQp9hOFdK' b'Kb+o9eEr6bfaop7re/fufSYSiXzvwQcfrAs2jsJNpFu3bo3m8/mOvr6+duBbgWAwQmDLlFu1' b'9RGaar/DEOxq6QHYdhbfPMcI1BCew/L2mwu8zmLV2MGXaB6t6A9KKWUgxa9eELiRP+AjHtw8' b'y9P0oLkBfwN4HniXu2m/3UbJYAPTV/VHwO8INTDvoKAPEYYKirC2BuYdQJU7eBDOtzDkar4q' b'2JoBA5CEQMPHwf+DcP6xy3Lg6v+3gwP4L297s8Cw4xrlAAAAAElFTkSuQmCC') index.append('knob-disab-sm') catalog['knob-disab-sm'] = knob_disab_sm #---------------------------------------------------------------------- knob_trans_sm = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAACEVJREFUWIXNmGtsHFcVx393XruzXsd2/G7tpIbEpapUIRFR' b'KZFQ1YBUCVSFQKUIlbYiKhUSVZEA9UMpBfohH0gUtXwDRENbSqgQuJZCH0lMyyOKSGr6MnUe' b'TuzY3SSt147ttXdn5s69fNiZ9XjspClxJI50tLOzs3d+8z/33jnnCK01/89mrPaAmzZtemI1' b'x7sqQLFohhBisxDiy0IIRwiREUK4Qohc5HVa661CiLwQoi5x3o2udYQQthDCjMYSQghxTYDR' b'ACK61gRywAOADTiRZ4Bs5EHqe3zcB2wHrMjNaMwrQlpXARZ7POC/gRC4A/hX4rwBiDAMzwJ1' b'gI5cAb1AD3AweqAwchV9aiGEBrROLQqx0iJJwcXKmYmn37Jr167NW7Zs2S6E2KiUIgzDmsff' b'lVL4vn967969w0eOHBkEfgXISGUZeRJ2GeQywBTcF4DPAM9HYM7hw4fva2ho+HYmk9mglEJK' b'SRAESCkJwxAp5RJAKSVSSkql0sj4+Pi+xx577AXAjyD9FUCXQK4EaCSUuxl4Anjn4MGDlba2' b'tgdd190ohCAMQzzP46233mPs3DgzMzPIUFbngmVTv2YN7a0t3NDZShAEBEFApVKhVCqNjI2N' b'7du9e/fzUVSeB74GTEegKgJUywAj9eL5ZFFdCB3t7e2vHjhw4Kbm5mYAisUpXn7lIIUPxlEq' b'xLSyWI6LZWUQhiCUPjKoIIMypmnT0trBuq4OlJJ4noeUkoGBgaf6+/s/B7wC/BooJ0KvAKW1' b'1lYKLrkgLMAeGBi4u6Oj46ZsNotSioG//p3jx45imA6ZfBuOk8cwLQzTxDAMDMOM3EAYgsr8' b'NFNTBT68MEH3uh5yroXv+3R2dj5yyy239L///vv7E2LohAshxKKCCfXiheC89tpr32pvb9/t' b'ui5SSl7q/wujZ0ew3SbsbD2mYS0CrQBYPWcihGBhbpLihdOsbe0glxEEQYBSihMnTvxk//79' b'z0UKVlLzUiX3wXR4nYaGhocsyyIIAvpeOsDZs6exc62Ydl10+dWZEAb1jZ3ccNNnmbw4wXw5' b'rC2u7u7u+1ncL53o3rVtKw34DeBFYMczzzzzkOM4G5VSvP63fzI6egYr24ww7KsGS1s210jX' b'pzZxafojNA5hGGKaZs+2bdt2AA3R/X9BYhNPA/YBTwOfP3r06MNCCKamp3n7rTcx7Pw1wcWW' b'y6+lpWMjxeJFwCAIAjo7O+8DXgU2A7+7HKBBNe5v9vf3D+/cubNFKcXhw6+DsBBm5prhYmts' b'7sK2s2BUVXRdd/3WrVufBX4EDCUAjZXmoJnL5R7UWuP7PhcvnEeYLp9kzn2cGYZJS8enmS/N' b'oLVGSsmtt976dRbf0ZdV0ABMIcRGKSVvv/MeWiswnFWDi62xuQsZeDiZPGEY4jhOD8sTiRUV' b'NOJX1rlz4yDMVYeD6sp2MnUgDLTWhGEISxMPAxC1jbq3t/fGIAjcIAhyQRBgmiazs7PXDRDA' b'rWsg8Mq1JKOpqanNcZw1tm1XyuXyeLFY9GqA+Xz+t01NTeu01lahUGD9+vXxU103s50cXmUO' b'KSWWZdHb2/vjxsbGdWEYBoVC4YfFYvHlGuDg4OCXHn/88S96nlff2tr63PWGq5pGa2qZUKlU' b'+untt9/eYxjGh4cOHXoDUgnrk08++Q8g19fXRxiGGIYB1xHU9+YxDLMW4qGhoUtDQ0NvAKX4' b'mhVT/vgPdfk86OsHWFmYAxYVZDFRqFkSsJZJxKu4rbUlAlz90lSpEM+bZ2F+Ht/304A1Tyuo' b'ATU1NXWmUqnQ2dGGEAYof9UBZ6cL2HaGwgdnCYKAiYmJAtWCTLGY/i9T8GbgB48++mhmdHQU' b'z/PI1zegwzKrqaJSIR9dOI0wbMrlMkopjh07VgD2At+hWnQpUgoq4KvAzMzMzLampqaRSqVC' b'140doEO0rKwa4PTkOQK/wulT/4nrmbHZ2dmHge+zmDQrUvmgBn5GNd25MDIysq+angc0rW1B' b'yRJ6FUI9PzfJ5IXTVDzJpekphBCMjY29SDXdLwB7gEkuo2Bcp8o9e/a8UCqVRjzPo87NkKur' b'R1aKqPB/h1woFZk4cxyNxdC7g0gpAcaGh4f/xGKlF1d4KyoYAwaAf+bMmX1RbUs+l8HN1SPL' b'k0h/Dj5B00kpyfRHo5wfexsMl8HjR2rF0/nz5/8QwcUeJAB1siaJC3SLauqdBdw777zze93d' b'3Y+0t7ejtSaQUF6YQxg2mdxanOwazCsUTeVSkdnpAmHgMV/2GXp3EN/3cV2XwcHBgpTyN8Dv' b'gQWW1yXhFYsm4AZg14YNG/Q999xzt+d5hGGI1qAwkL6H1iGG6WDZLpadRQhBKL2o7KxgWg5K' b'G5w6OcTMpek4QaVQKPx8dHT0deBhYIBqfbysaEoDJiFt4BDVuvXZ7du339vV1XW/YRg9QRBE' b'oBrLzkbCK9AajUAIgQIWSnOcL5yjXC4jpayqKsTYxYsX9586deqPEVAz4AGnUvNwaYhTKsaQ' b'TdF8sKKQZ+66664dPT099woheuI2R9zqSLY/pJT4vk8QBGitUUqNTUxMvHjy5Mk/R0CV1Gca' b'Tmmt9ce1PmIl41ZbJnJn8+bND9i2/c3bbrutNdk4SjeRFhYWRk+cONE3PDy8A/hKYjF4KbAl' b'yq3Y+kiFOu4wJLtaTgJ2B9U3z14SNQSLxUuy/aaAp6hWjX18gubRsv6g1lon+om1PyRuFA94' b'RwQ3x9I0PWkq4U8DDwEvcC3tt8somWxgxqp+F/glqQbmFRSMIdJQSRGuroF5BVDjCp6Eiy0N' b'uZKvCHbVgAlIUqDp4+TvSbj4WLEUuPb7kpCm2tX/BV0HrAK/xpabAAAAAElFTkSuQmCC') index.append('knob-trans-sm') catalog['knob-trans-sm'] = knob_trans_sm #---------------------------------------------------------------------- load_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAMtJREFUOI3tlLEKwjAQhr9IERUFB5/Byd0nSWdXB8HnSgZf' b'QHTt6ubm5C6Cg1g8l9SmtKEKHSp405+78PHfHYkSEZqMTqO0nwBG/kHFdgVMA3c3YvSuDqiy' b'pajYzoA5MACern4HUqcPwMnpmxj9qAMegQTok4+iF9ApMALWYnTiA/2Wz2L0oq6lt5PYbl0H' b'hfCX0v0UljHJR1MJ/DaGwLVJoAJKz6xVDqFihv6WJyq2e6fHAYCfj6hw6AOXnr4EgIW8GF1y' b'qP7fV/uAL0qsNUpB0my9AAAAAElFTkSuQmCC') index.append('load-hover-trans') catalog['load-hover-trans'] = load_hover_trans #---------------------------------------------------------------------- load_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAFtJREFUOI1j/P//PwM1ARNVTRs1kJeBgeE/AcxBioE5DAwM' b'ExkYGBhx4CoGBoYOQgYyIiUbctIPI7oACyEFeABWBwytWB4hBqLHMsVFD7KBpCQZnGDwh+Hg' b'NxAALDMTOXtBqyUAAAAASUVORK5CYII=') index.append('load-normal-trans') catalog['load-normal-trans'] = load_normal_trans #---------------------------------------------------------------------- Mario1 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAA3NCSVQICAjb4U/gAAAADFBM' b'VEUBAAD4OACsfAD/pECFmYJ4AAAAAXRSTlMAQObYZgAAAE9JREFUGJVdjlsOADEIAkXuf+da' b'fKWdZD+YLgazC4QNGEp4QDptcgTqW/EYFSIHXWKxJx3o5/oX1+hItUGpFDPMqrZDU+ATeTF9' b'i9mzOzYdr7MBOykYeJMAAAAASUVORK5CYII=') index.append('Mario1') catalog['Mario1'] = Mario1 #---------------------------------------------------------------------- Mario2 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQCAMAAAAVv241AAAAA3NCSVQICAjb4U/gAAAADFBM' b'VEUBAAD4OACsfAD/pECFmYJ4AAAAAXRSTlMAQObYZgAAAElJREFUCJlVTgEOACEIQvn/n4/A' b's8VqgwIUQBkwaiHRAtmEuQh9I1bZJC4cIwcJdc2zy7oqKpw69yczj0PpWUhqeZb765J6ys0+' b'd4kBE9uuw3cAAAAASUVORK5CYII=') index.append('Mario2') catalog['Mario2'] = Mario2 #---------------------------------------------------------------------- Mario3 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAMAAADjyg5GAAAAA3NCSVQICAjb4U/gAAAADFBM' b'VEUBAAD4OACsfAD/pECFmYJ4AAAAAXRSTlMAQObYZgAAAEVJREFUCJlljosKACAIA+f2///c' b'sPWig6TDKQKmGoTatNJIFGL+qt/SyztqMzOugL2KxejMMaOuFU30PeK+6WtmNw5cPgCA2wEW' b'cesrCwAAAABJRU5ErkJggg==') index.append('Mario3') catalog['Mario3'] = Mario3 #---------------------------------------------------------------------- Mario4 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAA3NCSVQICAjb4U/gAAAADFBM' b'VEUBAAD4OAD/pECsfAB81QrqAAAAAXRSTlMAQObYZgAAAFFJREFUGJVlj1sOACEMAoHe/87b' b'1j7MOokfTNAgEDBBwaENZJI5HRXCT5nOIxBlpbruJFjMSKtnT5dHRC2yR7FeivpMQ4nf1Ftw' b'RX5pl+zCN3+wAgFJZUDctAAAAABJRU5ErkJggg==') index.append('Mario4') catalog['Mario4'] = Mario4 #---------------------------------------------------------------------- Mario5 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQCAMAAAAVv241AAAAA3NCSVQICAjb4U/gAAAADFBM' b'VEUBAAD4OAD/pECsfAB81QrqAAAAAXRSTlMAQObYZgAAAElJREFUCJlFjgEOADEIwgD//+cD' b'nSfJknZTJ+Cwc7QGlVROUBEf23ILUqDWV9fBxJechzDDU0ZqbH+tkx72L2R562QYj7fjHPgA' b'edMBLXH5xvAAAAAASUVORK5CYII=') index.append('Mario5') catalog['Mario5'] = Mario5 #---------------------------------------------------------------------- Mario6 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAMAAADjyg5GAAAAA3NCSVQICAjb4U/gAAAADFBM' b'VEUBAAD4OAD/pECsfAB81QrqAAAAAXRSTlMAQObYZgAAAEhJREFUCJltjoEKQCEIA+f2//+c' b'mksevIOgy7UCkmiA3a+DIpVcYWmudtsoKsY+eOnGTVToU6V7w9MinJ9fOB7P8TfG9I7uMweB' b'BgEmBijSmQAAAABJRU5ErkJggg==') index.append('Mario6') catalog['Mario6'] = Mario6 #---------------------------------------------------------------------- midi_click_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAktJREFUWIXtls1LlUEUhx9FSEhIqlUlzZk/wBYRRES2nCA0' b'sHIjJhG0NKgWBe0KhHDRMoLU2gWCLWqmWpTVrk21rpnCICQLC9tIMC3euXCRqzHvyzUX9wcD' b'83EO52HmMOe0xRjZzGr/3wD/UguwqlqAVdVRxdmJ7gbOAEeBI0A3sAS8BOaAKRP8UpUYpW/Q' b'iR4APgAjwHNgH7AdOAS8As4DH53oE1UA28r8g070BaA2HpvgV5zoLhP8cp1NB8XtXgfGTfC3' b'ShHGGLOGVTJqlfyySnpX7e+1Sh41sD9glSxaJaO5sWKMeU+ccm4cGDLBv29g0rl6wwT/BrgC' b'TCT/LOXm4AjwxQRvG5z9Bj6t4TcJfKd48izlAg6kYDjRyolWtQMT/CJwuZGTCf4PMJ38mwp4' b'FJh1oruAS2ldD/Kjfu1E33SiT6alBfqaDdgOLFDkWi/Fk6+n/cCNlHufc+FqAXMUgZ70nK8p' b'8m49dQLfAAXsyqYjH/AFMAhggr8KTKxlmP7B0yb4wyb4t8AwRXVpKuAsMOxEb0nrSSd6fg3b' b'eeBZgt0K9Cf/pgJOA7uBWvm6D8wkiNtO9Dsn+lo6mwVcmg8DO4GpXMDsUudEj1CUr3Mm+Kd1' b'+zMUdXgBOA50UTQOB4E7wEUT/L1cwOzSk8rXmFWybJUMNjjbY5V0pPmQVbJilYyViRNjLNcs' b'pBvrB+6mG3sAPAG+Aj3AMeAUsAM4a4J/WCoIJbuZOshtFOWrL41aPziXxjTwE8AEv/GAG6FN' b'3/K3AKuqBVhVmx7wL+qmck+9L15FAAAAAElFTkSuQmCC') index.append('midi-click-trans') catalog['midi-click-trans'] = midi_click_trans #---------------------------------------------------------------------- midi_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAkNJREFUWIXt1sFrVUcUx/FPRGjAYIN1JUrzF9RFEUSkI7iS' b'FpWr1k1QEaFLheqAQjdaQW7Joi4EETSpqxYddGOwLuzY7rqRrltQFGzQlBQiSAnExZ3AI5jI' b'zeOlWeQHs5iZczhfzjnMmb7Z2VkrWWv+b4D3aRWwW60Cdqu13ThXdR7EUezCZxjEFB4hYzTF' b'MNVNjCVnsKrzPvyJI3iIrdiAHfgVX+Gvqs77uwHsW8o7WNX5FObWvRTDf1WdB1IM0x02azXZ' b'/RaXUgzfLwtgVedjuIydKYY/Os4/xpUUw+fz7LdhHKdTDKNtAVuVuPTcJRzuhOtQ//yDFMPv' b'OIuR4t87QE2/PU8xjL/j7jWeLOB3A5OakrdSW8B9JZiqzkNVnYfmLlIMr3DmXU4phhmMFf+e' b'Au7CnarOAzhd9p0g/3Tuqzp/V9X5YNmOI/QacA0mNL32iabki+lTXCy997Qt3FzANprFllLO' b'3zR9t5j68RJD2NSaTnvAX3AAUgznMLKQYXkHv0wx7EwxPMawZrr0FPAOhqs6f1D2N6o6P1vA' b'9hkeFNh12Fv8W6ntLB7DN9iPH3ET6wvEVWzHrRTDhQLzpvgNYyNG2wIuZZIc0YyvEymGnzvO' b'b2vm8AS+wIDm47Ad1/B1iuGHngMWmJO4iKMphtvz7jbj7xTDTFXnw5osn1m2WdwBshfXNRn7' b'CffxAluwB4fwEY6nGO4uKUg3gAXyQ834CmXN/QdzWWOT0zP/Qj6/e/kBl0Mr/su/CtitVgG7' b'1YoHfAsYmcVv10YFFwAAAABJRU5ErkJggg==') index.append('midi-hover-trans') catalog['midi-hover-trans'] = midi_hover_trans #---------------------------------------------------------------------- midi_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAfhJREFUWIXtlj1rlEEUhZ9o/AAFF00hJhJLKwVBBBvFIpAm' b'CajYhFUUsdwUWvoDxNbGxrikE2JiISpiRPQHaK8QjBA1CahoI4bH4r0Ly7K7ZnZYFNkDl/m6' b'h3uYuTN3+lT+ZWz62wL+hJ7AXPQE5iJXYAmoAHPAGrAe7RwwFetZyBE4DrwFysBz4DCwGzgO' b'vASuAO+AiSyFaic2pS6qE+rWmNvZ4NOvXlKX1UqHcToSeEH9ph5qmB9WHzbxP6quBq/rAkvq' b'R3W0ydqw+qwF77K6FvykmKk5WAY+AI+arP0AFlvwpikuz/nEeMkCxyMYwIGwGlaBay14v4Bq' b'8NOQuOXr6mBciFsbyKub6pnoHwl+Usw+034zAlso3rf7sTOn2vgvAIPAMWAz8DnaDSP1iAX2' b'UxznK4q8a4ftwApFKuxLjBUR07Z8Qb1aNz7ZxrdfHaob3wh+V2/xPDAJbIvxNLDUwncJeBr9' b'HcBY8JOQKrBKkVO18jUDzEb/NvAGuB7jeeBx9CeBAeBuqsDkl10tq+/VkYb5WYuy9jqO9qC6' b'16IcrgSv65WkZhX1u3q6ydpQ5B/qOfWnGbU49ZmpxxhwB/gE3AOeAMsUt3wUOAvsAS4CDzoN' b'kiMQYBdF+ToRVgK+AC/CqsDXnAC5AruO//7L33X0BOaiJzAXvwE8vKSep8MpcwAAAABJRU5E' b'rkJggg==') index.append('midi-normal-trans') catalog['midi-normal-trans'] = midi_normal_trans #---------------------------------------------------------------------- next_24 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAABipJREFUSImFleuP1Fcdxj/n/C4z85vbzszO7C67XJZ1QdYC' b'bbmWokJDWzXGW1+Y9B8wqW0lNV4SE0PVN9qmWpsmLbX2ggUiIrZpSrVKCNiWCqTI4roLLLAL' b'7M4ys7vs3C+/c44vRsrFNj7JyXn3fL55znnOETzOzdI37BYb0pHMfYtivSsTwUSfZ3lOWZWb' b's/7MubFjFwZzU7m/8A6HKfCJEv8D8AHJ5vXdG36wLrN+yzxvvqWVRmuN1gaNBgE6qMg2JvWx' b'00cOHP/jsV9wiHf+P0BhZcId27659MEf9keW2jOVWQq1Ag3dxKDRxqCMxmAQQDgQoS0WZ5zz' b'+vU/7X0qvzv3Y6ap3giw2PzR5M6iZO/LD6/Y+nBUJOTluQmqzQoK1TLHoIXGCI1Go4Sm4leY' b'rsySoF2sWr1qw3j32LLC0bm3aNC4Dvh8K5Z0NP2TR1c+9u1GzSdXzmPQKBRKa7QwlGtlCqUi' b'DdPAci2UaEG01BSaBWhIVg2sXjYih2LV49W3MdcAm0BKec+3Vjz0gmciIl+ZBgw+Cm0UCk25' b'XmF+YB5fWf5FdENxOn8WO+KihGpBLENJl7F8h8V39K0dPHPilLlohlqAz2Gv7V732sbMpp6J' b'wiRGtMyVaS1fKBrlOt+/fytrl6xmQ/96xrOXOJUfIhAPoi2DtgzGEhRVmXnhbqodxYGpv2Z3' b'o6hJ6ci7P9u1ad1MeRZtNEpfM9f4QqGkxpeaWrMOgG3ZfO9L32HTvI1cyk1QETWq1KlRoyEa' b'XC5OsmL56tvkHfI+AGv+Vxc8srnz3g35ynTr8Ggdoi99fPxWxqbI+2ePsHrBnSS8NixpcVfv' b'Go6c/Qd/GNlH1uS4VLrMRHmSC8WLeKEwc4V8pXqs8oa9MLpwja/81uT/Na/7DWqVKspoJiqT' b'TNSnyJcmmdqTZ9eDv6Ur1oFruzz3wK9gH7w6vpt4Mg5aUFc1JgtZEgPJlTN2vt1OBlPztDa0' b'7A0KjagavrvpEZZ099NUTaSQSEtSV3XagvGP7njAdtn+9acJvumyd+oNAtEQAd/BsW3cUDSN' b'JC61MGlf+yAECDg6doygG2JN/yriXoz2aIpkJEFbKE5HJEPADtzUVMd2eOL+n9Ep04Rcl0jQ' b'I2i7yIBMIPBsjJmVQsZ95XPownsMT52gVC4zMnmGRe0Lbiq9b5pY0iJoB2+C7Bnah4xAPBRB' b'KUU05FEsVCtotF1UxSnf+Iv+duYgo9OnIRxmtDnK+u1b6El1Iy2JY9ko7dMVyPDLLz9Bf6oP' b'A1gSdny4k+fOPk+qI4FEUPebJLwY0/WJHJqyfTE7/uH7paPrRqeGIRRqRRVyuRq4ytXmLBgH' b'SwvSjSTbNm6jt62PSkPjSsmOwV28PP4iXQvSOMJBG0NDNUhG40yP5EZQ5OWFd8/vz8sruMEY' b'SAMWYAlwHGw3RCgQJNIM8fO7n+TeT21hturT9CWvnNjJa5dfomtBhqQXJ+5FiHlhOqNpLCHI' b'HskeBopSXVQHSvXZ4Z5UDxgFUrQgEqQQSAme9OhLDFBrgPZtdp78Hb/PvkLn/AwJL0abF6Et' b'HCXuhelNLWRo6NRl/7h/ADAWkzSK8bly3+39X7taKtMUdQhIhC2xLQvXdjFCUy2UibtJ9o++' b'zpvTu0j3pGgLRYkEPaKhMOFAgEy4nbl6kT0/3fuMGlb7AN8C0MN6sLGiOrB08WcGrpSm0ZZC' b'2ha2tHAtm1A4yFj9LO9OHeA8QyQ7EsQCEcKBINFgmJjn0RlJEw5GefbZ7Qdzv8k/DuRoBQEo' b'VO6lKw+Nz4z8/fbFy4m4UbT2AQMIMIJgJISdtAnFwlhYCEGrgBLavRTxUIJfv/r84OhT534E' b'XLj+H1xTgUrpX4X9zfmVTy9ZtnRJwPaoNKsYoVtRWQ6u7eBYDo5j4doO0WCEnlg3hXqJJ59+' b'5vA/tw0+xhwfAOp6e26VheM+4D666Av9WxOZzh7lt15YYRkCrkPKayMdSZKKxrEsycl/n8we' b'fPHQjuq+6gvAuRvNPx5wTRl6nTudb2Tu6ron0Z28LerF0gEngCUFtXp1+sr57PDYobH3Gh80' b'/sxVTgKlj7P5ZMB1BXHpRBIHJAZQ1PDJAzO3Tnyr/gNyndAFrbDhdwAAAABJRU5ErkJggg==') index.append('next_24') catalog['next_24'] = next_24 #---------------------------------------------------------------------- open_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAWCAYAAADJqhx8AAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAIhJREFUOI1j/P//PwMlgIki3aMGYBqgXrDDgVIX1KsX7Liv' b'XrAjgVwDGBgYGBQYGBjmQw0i6CJ8YaDAwMCwX71gx358BhETiA5Qg/KxSbIQYcADBgaGhJsT' b'PA4S44ILaPwJDAwMBrg0Y3PBBySDEm5O8LhIyHnYDCi4OcFjIiGNMMA4mp2HgwEAK5Unw3iO' b'ZHcAAAAASUVORK5CYII=') index.append('open-hover-trans') catalog['open-hover-trans'] = open_hover_trans #---------------------------------------------------------------------- open_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAWCAYAAADJqhx8AAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAIZJREFUOI3tk8sJgDAQRJ9iIZagHaSUdKCl2JHYgVagJdjB' b'eFGQNfGDN3FgDkmYx+xCEkm8Ufoq/QOCAPeYIGnvVtIoyZv7qEOATaMkdwU420EOtKvjo500' b'sKpCDbIba5oAD3ShRztCb84NUMTCwKHBvAN5YLiqZxvMQA2Ud8IAyf+dvwBYAAo3rlh8Dbv6' b'AAAAAElFTkSuQmCC') index.append('open-normal-trans') catalog['open-normal-trans'] = open_normal_trans #---------------------------------------------------------------------- path_click_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAcCAYAAAATFf3WAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAASNJREFUWIXtljFKA0EUhr81apGk8ATqTGNrlSPIEEjhFUyr' b'YGdpZeMZLCyDIFjJnMIDWMxAiii2okViWIu1WATjG56aRebrdnlv5ts3w88WZVnSZFaWLfAd' b'WVBLFtSymlLsje189BQLyl5dDFOVVY1CGjPe2D3gAOgC6wtKL4ETYOBiGGsFRRP0xm4CR8DQ' b'xfAkqN8CToGhTk84QW/sMbAD3CD7qDZwDhx+en/nYnhIEZTeQQe0gH1gLuy5Bfq15y5wBuyK' b'7ZALPgMjF8N1yuJ1vLHbwFVqnzRm5sBb6uI/QeNzMAtq+TeCL8DsN0W+Qiq4RpWDf07jj1ga' b'1BOg543tKfbaoDqJJKSC91TTTt6gxiNwkdok/t1aFo2/g1lQSxbU8g6Glzctx5BiaQAAAABJ' b'RU5ErkJggg==') index.append('path-click-trans') catalog['path-click-trans'] = path_click_trans #---------------------------------------------------------------------- path_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAcCAYAAAATFf3WAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAASFJREFUWIXtlqFOBDEQhr894MSxgicAHBZ1to4QEkRfgbOQ' b'4CpRiPIMiJOEhBSF7wvwAEgEECwBAWwWsYgNCXfTDLAb0s/tZqb9dtr82aKua/rMoGuBeWRB' b'LVlQy2JKsfVx+bOnmFH2Epx5VVm1KKQxY33cAvaAEhjOKJ0CDtgNztxqBUUTtD6uAgfAJDjz' b'KKhfA46AiU5POEHr4yGwAVwi+6gRcALsf3l/HZy5TxGU3sFtYAGwQCXsuQJ2Ws8lcAxsiu2Q' b'Cz4BZ8GZi5TF21gf14Hz1D5pzFTAe+riP0HvczALavk3gs/A22+KfIdUcIkmB/+c3h+xNKjv' b'gLH1cazYa4XmJJKQCt7QTDt5gxYPwGlqk/h3qyt6fwezoJYsqOUDfzA4bYXlJWgAAAAASUVO' b'RK5CYII=') index.append('path-hover-trans') catalog['path-hover-trans'] = path_hover_trans #---------------------------------------------------------------------- path_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAACgAAAAcCAYAAAATFf3WAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAJBJREFUWIXt19EJgCAQgOHfaIfemqIZGqA9mqEp2qFpgtZo' b'CXsPotMrFbkffBP5IurQee8puSY34C0DajOgtqqAI+CF6wD6lMAemIEOcIK1AUtK4ATswCnc' b'vwJDlOiWE06SL8eNC9nc/nXwQ8EPWtVXnCUDajOgNgNqC/lRZ7m8SIFfTJGoin/FBtRmQG3F' b'Ay/nnRkH0sh4jgAAAABJRU5ErkJggg==') index.append('path-normal-trans') catalog['path-normal-trans'] = path_normal_trans #---------------------------------------------------------------------- pencil_click_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAXFJREFUOI3N1L1rVEEUxuFnxT/AUiyEmcJGAtoF/MJGp1ME' b'P5o0hsRCwU4LXQIxTRSFtRUEC7E0YuEUNjZa2qR1BqyDWIhYCGuxd+WyXvUSIviW58z85j1n' b'zsxgPB7bSe3aUdq/AO7uCuYQF7GKfZhLtWz2Bf7iMId4BSu4i2MY5hAPbQuYQzyNEUaplhG+' b'4AIebdfhNaymWu7nEE+YlH0YL3KIr3OIe/8GHLTHJoeYsIAN3ML1VMubJncTB3A11fKtl8NU' b'S8ZLPMbSFNbk1k1acK+3w5bT2ziJi6mWrVZ8D55gEyuplu9/dNhys4a3WJ+Jf061nMERLHXt' b'/e1gp1qGGOQQ73SkF3E8h3gphzjoBWx0A3M5xIczh30wKf0ZTvUGplq2Ui1ncTCHeHkazyHO' b'YxnncbS9p/PpdWgZaznEr3iPByYz+qrJD6cLO2+5SznE/XiHTw3geauSn33s/dukWj7iHJ62' b'YbPq7bCv/v8P9gfqe3o7IZI6AQAAAABJRU5ErkJggg==') index.append('pencil-click-trans') catalog['pencil-click-trans'] = pencil_click_trans #---------------------------------------------------------------------- pencil_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAXBJREFUOI3N1D1rVUEQxvHfFT+ApfgBbCSgxYLgGzZqpwi+' b'NBZrSIQ1YGcKvQRimigRrsU2gouF2AgqFrGwsbHZwiZtGusUKUQshGtxbuAQT/QQIzjlvDz7' b'n2VmBuPx2F7avj1V+xeC+7ucMddpLOIQpkoKa30FfyGMud7CAh7iFIYx16O7Eoy5nscIo5LC' b'CF9xFU93SziHxZLCSsz1jKbtY3gbc/0Qcz34J8FBe2xirhdwA29wD3dKCh8nsXkcxu2Swvde' b'hCWF93iHZ5jZEpvEljVf8Kg3YYv0Ps7iWklho+U/gOdYw0JJ4cdvCVs0S/iE5W3+zZLCRZzA' b'TFftjoNdUhhiEHN90BGexumY6/WY66CX4MTuYirm+mTbY+ua1l/iXG/BksJGSeESjsRcb275' b'Y67HMYsrONmu6Vy9DpvFUsz1Gz7jsWZGVyfxYS/CFuk65rGCV5q1XO3K7X1tSgpfcBkv8Hqn' b'vM45/Bv7/w/sT4ebdkuJ/xa5AAAAAElFTkSuQmCC') index.append('pencil-hover-trans') catalog['pencil-hover-trans'] = pencil_hover_trans #---------------------------------------------------------------------- pencil_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAQdJREFUOI3l1C1LREEYxfGzYjAaNpgsBoMGi2gQjBotC2YF' b'xWQwKGITP4CatYnYfKkKJotF8BsINpvF9jPsVa7rheUui8UDD8ycZ/gzZ+CZBtJPDfSV9pfA' b'1SSvSSSZrEVEZ63jBZuYwwWmKs5VVqexgA9sFfspbT32CrzBbrGex3UB3cMtRuoCF3GGFp4K' b'6FdvGycYqgMMlvGO6YreIY7rAlNEvEOzwx/GFQ4wWAcY7BcRq3r32KgLDE4LcKc/hvPieRp1' b'gE1c4qiit6ithbLfbfTekiwlmUiyUvJnk6wlaSWZ6zYpVVWOOI6H0g3ViVyuUbziGUt+qidg' b'MIMdv/V9psF/+2A/AUoiASS9tiF/AAAAAElFTkSuQmCC') index.append('pencil-normal-trans') catalog['pencil-normal-trans'] = pencil_normal_trans #---------------------------------------------------------------------- play_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAANRJREFUOI3V00sKwjAUheH/FgUdO3MBDe7Cod2I02QpdtpN' b'OEy2Yue6haKg10Gt1kesoggeCJQSvl5OE1FVvpHkK8r/QsaFziJ77yLGhTGwnlZLKYqC5mdF' b'IeNCAuxv35d5tgEE63U+R+4mMi70gV3XhKn1WuaZlHkmWK9QY+2OBl3IaSJJa+AqbejwChRL' b'Enn+COrsBy4dRaHVYrYFhsCIuq/JI6hBUut1Wi3PoDy7tMaFAVCdPiQAInJG2ucIVX24mqgq' b'qfUa29espxO9kz+9/T+Fjl/1d79zyMbJAAAAAElFTkSuQmCC') index.append('play-hover-trans') catalog['play-hover-trans'] = play_hover_trans #---------------------------------------------------------------------- play_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAKlJREFUOI3tlFEKwjAQRGeCYr/99ASeLLm5hxDa8aPNuilJ' b'LFgEoQMLJZt9TCdNKQl7KOxC+WvQxyC3gAoIyRsApZRA0tZPrWmSQdJYaT1IUvNxG4n5+Eme' b'JT07zvKQuFjxMP9qQwfinWY3hTxo2gJqKTSevwL18jFJsozWDSvMOV0BXADcVQo+GkmKMb5n' b'PWhdAIYaKEO8Eapx+7P7pV98M9X9x2/kd6AXuPeEtJwbEd0AAAAASUVORK5CYII=') index.append('play-normal-trans') catalog['play-normal-trans'] = play_normal_trans #---------------------------------------------------------------------- pointer_click_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAVVJREFUOI2t1DFrVTEYxvFfxaWDFDo5iYlKdXDyI3QwkyC4' b'C4Kbg25OUvoRLE4OhYrFqYtLhoKDlS6iTuKUQBHpUK6oaAWR63AOcjnWcs/hPlPyhvzzvG/e' b'ZG48HpulTsyUhpOTkxziAm5iM9VyMATYdbiIB9jNId4aApzr1jCHmHEVI1TcTbXsDHUIz/C1' b'dXsFT3OIj3OI54Y6PI1XiBPhQ+xhA49SLV+mdphq2ceLTngeS7iPdznEG1MDWz3X1LCrUziL' b'9b7Al/h0RPwH3uJSL2CqZYTtI5Z+4Umq5WNfhzRp77fjET5jAXdyiGkIcBff8RO3NXX7prn9' b'tRzihV7AVMshHmIl1bKFVey0B5zHVg7xTHffP314nHKI83iDi5re3E61XJvK4TGur+O1pjeX' b'c4hLg4Et9APuaZ7nmuYF/VWvlCeVQ7yM96mW3zMB/k8z/7H/ADscb4jM90ClAAAAAElFTkSu' b'QmCC') index.append('pointer-click-trans') catalog['pointer-click-trans'] = pointer_click_trans #---------------------------------------------------------------------- pointer_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAWRJREFUOI2t1MGrzUEYxvHPlY0o4dpQStze1d24ZWHFbiJl' b'YWODLJRQc7NXspJOTZJkwU5KqZPNkI3c8h9gikiSIlsrHYv7W5yO3O7vOM9q3nea7/vM2zsz' b'NxqNzFIbZkrDxvEgct2KM3jYSvoxDXDS4XZcwjByPTcNcG6yh5HrI1Sc6gpeayWtTOtQB1vE' b'cdzGlch1ELnu+x/gAra0koY4j68YRK7LXZ/XD2wlfcN7HO7i762kAZaxHy8i15N9HMJLHJso' b'9LGVdBHPcKcv8BXmI9e948nIdYhDONAL2Er6ibdIHWhn5LobO/C0lfSlr0N4jqOR665ufROX' b'sRS5pmmArzGPFVzHJ1ywOkpXI9eFXsBW0i/cR2klPcENbMYR3MWDyHXP5Lm/Xspailw3WZ2A' b'W9iGpVbS2XU5XMP1aZzABxyMXGNqYAd9h4J7eIzP4/u9rjyuyHURb1pJv2cC/Jdm/mP/AXss' b'dosPEfREAAAAAElFTkSuQmCC') index.append('pointer-hover-trans') catalog['pointer-hover-trans'] = pointer_hover_trans #---------------------------------------------------------------------- pointer_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAUtJREFUOI2tlL8rxHEYx19nu+ny40pMl2S6ASXpJtMtIill' b'kBSj0v0FSjYGYSCbzLhFmSQMikWUQuksBqtJL8N9r871PXffc+96hs/zfD7vz/O8n8/niak0' b'Ey1NZQshTABLQEfDjGq5pdQn9VKdr4jVZZUZvgK3wB4wDZwBmSgJhml4CqSBMWALyAHrQE8j' b'JaN2qidqW7BOqjn1SF1WE3+VXC2woU6G6Lut3qhTUQnH1f0qsVX1o96mlHBB8emkKvzHwAgw' b'EKUpAJ/AI5AN1kmgG2gH8kAhSlNKNqrm1S71Tj1U+9UDNRtVQ9S4eq2+BA1aU3fVYfVK7Q07' b'F6sxHBaAOLBJ8VvuAPfAO7AIzABv5QdqEVYiDpwHF7QCg8Bc+Yao0+YLmAUmgGdgCOj7taOR' b'AaBm1IK6Emhdt4Z/IQ08AN/lzv8QhqLpE/sHFJ/dsH79xXUAAAAASUVORK5CYII=') index.append('pointer-normal-trans') catalog['pointer-normal-trans'] = pointer_normal_trans #---------------------------------------------------------------------- previous_24 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAABg1JREFUSImFlduPlVcdhp/1re+8j7OHzC4OMzCIQIaZDnI2' b'lGlJKYmxnq6atGovqqUBrxrlotFEU5M2NibUiybVC1MSEkIabFICldE2ytChOAiVAC1HKTDD' b'dM/MZp9P31rLi41TJpT4/gHv867faQl+zXwZkEKijJKO7TyxOLVke3ds0fq0n+42kDFGz87q' b'2Ymbp26M37j92Yg+ov9CgYgHSNwHiMBxne9s6R7evbHrG5vTTidaaZTWaK3QGIwAGcBMNMP4' b'lY9OjP35+Gv6r/rg/wco3J5k7++eXvnDny70upmuzFBulol0BBi0MSij0WgAfDsgk+xg2pvi' b'4DsH/ji5d+JF8pTvBUi2ziX3VmUH/7RjYNdzsuUyUZqkETXQ7cwoo4mERguDERqFpmbqzNby' b'xKI469ZtWJvrvd0/c3LmCE0aXwAeAxQsSve8/sLArudKlQr5eh6NQaPmEjdNRKFUpFqrYmyD' b'sAUKjbEMpahM1NCs7v/6yivupe7KP8uHMO1nSh4F13a/tWNw5x4ZueRreYwwKK1QRqGFpqaa' b'1Is1vj/wJI/0beLK7asURBlsUJZGS0PVVBGRZPma5UNnr5y5qq/rj9uAYZwtvY/uXZfZ1D1R' b'msRgiFAoE6GFoabr1O6UeWHLj/n2um+y7KGlWEZy7NYYTsJFW6ClBikoqQpd4UOohc3lt47e' b'3I+iZklXbtuU3bw+V51BG41CobSaM6/eqbBz+Hm2DgzPNW6yMkXdblLUZWajPPmowJ2oQJ0G' b'N4oTrOwf7Jfr5XcB7EXJnu0Zt5OJ6uRcM7XQc+a7tsw333tqPz8ffYlyvIVVsgCDLW0sYyGE' b'AGNYbT1MZmjBttzxqbfsJYm+9VEUEenobmM1TdOidqfKruHn2brqC/ND599j96GXML7FgkYK' b'aUmEEBRNCSshsYSgqZpMl6ZJ9qcGc/bUAjvtpb+idHtSjAAD5Kdn2fnIT+aZA2zu28T5n41j' b'WzbGgBCAgYPn3uUX4y8jUhaecrGlxAkSXVikLY3JKK0QwsJg+PDaGPVmk+1Dj9+3lR1Bmkws' b'QzJIkgqTJIMkyTDJs2ufYVl8Cb7jEPcDfNdDulYSQWgDs1LKVC2q8cGVY1zLX+S69R9Gzn/A' b'E/1b5wFmq3mEEEgkxrQHXSI4dm2MqqzQEUuhlCIVxCncqZXQRHaRwmS1Ve07cmGEydINCH1K' b'Vpmn9v+AA8/sY9uKx+YAhz89yq/+9hsSmTihF+JaNo6U1K0GyWwMy7JoRC1SYYKp+mc5NGUp' b'lorVFae18ez10+D74AgIbOp+hUPn3mN9dh19nYsBeHjhAI7l8uH0GH7WxU+5eGmHRDpO6AX4' b'drv+2VQXZ46cPlY6Udoni7Io00MLnp4tVFBW1Aa4FgQOdafG4fMjbOhay5LOXrSGDT1raFQj' b'LlYu0JnuIOHHiHsBgevh2g6pIInWitE3R/9gbpljlj6lRwr53L8Wd/W0j5IFSECA64UUYiWe' b'PbyD9y+OYlntUvUkevGkS9wPSAYh6TBBRyxBKhajr7OX86fPXVbj6mj7VLRQJYq5pcNffapc' b'atIwVfAllm0hLYvQC2n5LUavjuKpkMnC57xz/W1kBtKxBAk/RiIMCT2PbDxLrjLL2788+Jq+' b'rg8DSgLoG/qTarbYs2Jw1ZqZcoFINhGOjWNJHGkT8wKIaU7mPuJkfgzToehIpIh5AUk/JBnE' b'6E5kEbbN719541DhQOFVIM/dYoCG+qX6+2pFs3/Fiv6VlWadmq5iWxLHdnCkje94xBMxYskY' b'cT8kcFx8xyPwXHqS3UjbZc/rb/z94quXd6O53F7B/wEA6jTLH5cOV1PFhV9bs3x13ElRixoY' b'FFJaONLGkQ6utHFsG9d2yAQpFqW6uVmY4rcv73n301cu7abF2XbktsR96wrIrfJHi7+37MWu' b'vu4hY1moKMK2JaHrkwripGNJOhMpWq0GZ8bPfPKPN0ffMsfNPuDWveYPBADg0iE3yiczg53b' b'OwYWDMXCeJftyqRjZKlWq+Zy//78wsT4xKg+oUfulqT5ZTYPBtwrhyyCFBBiiNCUUUzD/A/+' b'y/Rf/aq8FazYU2YAAAAASUVORK5CYII=') index.append('previous_24') catalog['previous_24'] = previous_24 #---------------------------------------------------------------------- process_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAYAAACaq43EAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAjdJREFUSInF1UvoTVEUx/HP9X7lMTA3u8nAI8lMyuP8ByaS' b'UhSp/51xywQRA0qSDrOTxEBJKQzkyiMGDEQUypYQBkomJPw9rsHZl+M6/79zQ1bt2metdfb3' b'rP3be51au932P2xE1cR6szUek/EqpMnAn4KHDQIZWZjX4nQP7uF4vdka96fg0opDmnyqN1vL' b'8C6kyeXoni+veDm2ItSbrSWohTQ53yu4VqZxvdlaiNMYEyFfsQudSg/iCfZjAKtDmpzsBTyY' b'xqMwQS7FvpL4hsJ8NBroCVyqcdy6zRXXeIYdvUAZ+lS/7Xp+Iq9qDNbJdwRCSJPrvYJ/0jhe' b'mR2Yh7kYH0OPsDykyd2YtwIn5Ds2gPu4gb0hTR5XAXdXPAWb/CrBqQ402iV8kB+2UZgdx3Ps' b'rgLuBrzGuZK80V3Pc+RbXrT3eFMFSsl1is1hOlbJqyfXez2uYwaOYWqMPccW3MaLkCaV4KX3' b'OH7AWhzpcr/BxC7fxZAmi6vAfguuN1sL5DoOr7DGezRxKKRJ5T/OUA3kSwS/lPfoBej08Gd4' b'gEUYK5flcHxHo9E4immF9WbiTpZlCzuOwRrIBazEeaxBH24WUraHNFmK1TiLbSFNvhTiR+KH' b'dsZk7CwySsERfgZ9IU0uhjT5jFuF8P2YcxzLQppcK76bZdlVea/v2JXo+25D/o+7NNsr3+KP' b'+N4kHh7oa9cO/EgqnJm1eKqkWoY41X/DGo3GRszKsmzdL8F2u/1PR39//6Qy/zfSUg4HG5tr' b'8QAAAABJRU5ErkJggg==') index.append('process-hover-trans') catalog['process-hover-trans'] = process_hover_trans #---------------------------------------------------------------------- process_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAYAAACaq43EAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAx1JREFUSInFVV9IU3EU/ja2clotfQrLMIki++eUMCOyzJSi' b'P1gWQUVFdAcrsP8RFYuIghVEGeFeZi89pkHq0AmtHlppREQP5cqpkTprrbvt3utu93p6yLvu' b'dAsdQh+ch3t+P37fOd/5LkdDRPgf0Kk/NBpN0osGgyEjKzNzdpBlv/EcJya7N+FGiCgWsWp0' b'Or26HoPBgIaGxtofgUCwubX9UVr6zPSJvPevSEgMAFarddux48fLAKCkpASyLHfSKJYWFi8G' b'gK07qirWllVUThlxTU3NBiJiiSjqcDhOezyek0TEKcRNzS13bt+9d0ImGuGE6PDWqurqKSG2' b'WCyVRCTTBFH38LFryqR2OOrPqh+XJJk4fngcae9Xf+8y0+o1kyXWjjWHXq+HyWRCfv6SsDrf' b'Mxj0dbz33gyHI7UAIkp+YKD/YyQ49CKZ2ZJCXYXRaMxwOp22YDDoJqKI0pX465c3zA8vD3M8' b'WJZFt89XrRpFdGBw8E1Ti7Nucf6yvJSkzs7OnsfzXNxsJUmmYChiU+6Iooi2trZMUplNwXnr' b'9YspSS0IQuDTZ59TndNqNaARefpPloUoiuA4Drm5uYUA0saIJyxakBNKSWoAmJM9L/3gEaao' b'1eW69bdrKdTl9e52u91zX3V0VkQ4fihmrn5/36kL1n1bdlTnL8hbOCslqdW4fuPGoXEW/vNv' b'x6G794trpjErYSOTJi5eu75UlCQpAXEi8DbbTUar1WomQ6xREypLYvW68k2eZ64mANPedfUN' b'CmzgffEqUykAPQD0+7/3Dfn9HwpWLC0HoP3246c7J2d+eZQPy0QEs9n8AECuSsCVAN7a7fYN' b'Me8kmvvL5+0uyzHLHrens3X7rr0Hdu6s2hwIBF4r53fu110u21hWeerMuf0fvL7mEyfPXYoK' b'nKx6oh5AqSpmA7jyT3OpodXpYnvySXNLraLt4aPmIiU/3TAjbpcqbzEM08gwDI3G07FSx+3j' b'sRiRpFg1165etbGhUJ8gDEfbW9u6lXxUiCRbwIcA9CTsFoif8VTDbDbXACiw2+2Hxx1O1IWp' b'BsMwxkT533IfNNyjqcHwAAAAAElFTkSuQmCC') index.append('process-normal-trans') catalog['process-normal-trans'] = process_normal_trans #---------------------------------------------------------------------- random_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAYAAACaq43EAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAedJREFUSInF1U1r1FAUxvFfpZa2qLOwrbrwBRGzUGmXuvJl' b'FTfFl7WiCxt3xg8g+AFEsh0QLH4ABRGMIlhE0IWgohSyKLpQqgjaqkhRMC4SYZrOlE6n1Qsh' b'Oefm4Z/n3Jtzu/I89z/GmuWIgjgdCOJ03T8H4w5urCg4iNODQZweaiUI4nQA2/CpE3B3k9xJ' b'bMBEC80JPEOtE3CzUg9i+98giNP+IE57yuceHMHd8uNWFPwba4M47Svjm3gVxGk39qIf97C1' b'E3CzUvfhGzbjDXoU63m8BD/AFw2lDuJ0P2RJ+HSp4HmOgzjtLcHT2FP+MrtxCacxgtuYMb/U' b'5zC0VOgCMDZhDq+xCwfwKEvChwrnM1kSvs2SMMfXIE5rQZzuU7i/3w64WuohzCLD0TI3Wd4v' b'orFpvFNswlN4kiXhXCfgwRI8ifPYiHHIknCy8u4sdmIYV9uBNgNvUWyk9wp36/GyhXYWIZ5n' b'STjdLri6xlN4nCXhT3xGb5aEH1pof2EU19qFUnGcJeFEQ/gdHxfR1hSup6oTURSNY0dDahgv' b'6vX64abgyriCH60msyQcXUR73cKWe7kx6Fqt8ziKols4VoYTjW5Z/rG4lHFG0WiouGUVHUMU' b'RRcwUq/Xzy6YzPN8Va+xsbFas/wfS7PVIdwUrGsAAAAASUVORK5CYII=') index.append('random-hover-trans') catalog['random-hover-trans'] = random_hover_trans #---------------------------------------------------------------------- random_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAYAAACaq43EAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAbdJREFUSInF1c1vTFEYx/FPhUYFs2mLiJdIY4O0O6y8rNg0' b'WmvCwtyl+AMk/gD/wF2xsiSxESIxsdEgQUgTi4YFRZrQQURI3C7OWdy5c2fM3NF4kpNznnPu' b'c7/P78l5GcqyzP+wNRXjRrFxEPBQRcVzWMRsVXCZ4iM42iVmFDuxVBUKa0vmTmMzGh1iZvEU' b'tUHAZYrHsCvnb8BwHA/jOO7E5P4p+A/WYST6N/FSqM7+mMhd7BgEXFbqEXzDVrwRVC5hJoLv' b'44vWUh+K/Vyv4KLi9RH8AfuEI7MXl3EWU7iNZa2lvoDxXqFl4C34iVeYwGE8xANB+TLeIsNX' b'QfWB2N/rB1ws9TiaeI2TcW4+9pe0XhrvhE14Bo9iwj1bUfFYBM/Hnx7Ek1wCj3PfNrEHk7jR' b'D5R2xduEjfReULcJLzrENnECz4Q9MRB4IbZf+Izt+Ngh9jemhZuubyuCG7nxd3zqElsTVC8U' b'F5IkuY7dualJPE/T9FgncN6u4keX9ekua9e0X7lX8k7V1+mvliTJLZyKbiOvlurvcS92Tjj3' b'FNSyioohSZKLmErT9HzbYpZlq9rq9XqtbH4F8SCzQi/Ay+QAAAAASUVORK5CYII=') index.append('random-normal-trans') catalog['random-normal-trans'] = random_normal_trans #---------------------------------------------------------------------- recycle_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAWCAYAAADJqhx8AAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAASFJREFUOI3d078rR1EYx/HXV8qvBUWkROSazQYZdFf5sTCZ' b'JMPd/A3KcCeL2aTYuAaLXwslmS4mKWWwG/haTrrdXCmD8mznnM/zPs/nPM+p1et1v4mGX2X/' b'D0BjeSNKsi7sYhz7WM/T+LgKUCt2IUqyPlygt6Tbwyq68jS+/rKCKMkagrAXZ3hEM6YwjQm8' b'YLjKwjIesJCn8V3Bzg1a0IHLsoUi4DRP483S+Sye0Y4mnJcBtZ9MYpRkg8HCSZ7G95WAKMk2' b'QqlH4banPI1fv4N/zkGUZK1YwBK2A2S0VMlkGVB8gwH0FNYHuI2SrB1DWMQM+qsAY7hCG0aw' b'grmg6Qya+UoLOMzTeCyAtvCO7pD8hrU8jXfKgB914bv4+9/4DwAf5ZJNUZG5OFUAAAAASUVO' b'RK5CYII=') index.append('recycle-hover-trans') catalog['recycle-hover-trans'] = recycle_hover_trans #---------------------------------------------------------------------- recycle_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAWCAYAAADJqhx8AAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAPpJREFUOI3dkzFKg0EQRl9SJdpo8TexErSxsRBbkRQ5Qexy' b'Ag/gKQQbK88gdjaCVRJJlV6ijYgxgloLwWfhXwxr3MI06gfLssvOm5lvdysq86g6V/T/BRRA' b'FxA4B3ayBDWOFfXBrzpTG+pmcp5KuMYqMAC2gT5wD9SAFlAHXoEXYO27CvbVU3U97BXqY6jk' b'Iq0getAD2sAo7LWBJ+CtXF+lFsQWcloFdvk09yYHOASWgcsy2zhkn63Qz4I6Dv3eznC9mXoQ' b'FxvJ1R2rdXVJ3VKP1LscoKMO1esS8K5O1OcA3csBinJeVE/UaQicqgdpcPqQfqTf+Rv/GOAD' b'lERinsbskcoAAAAASUVORK5CYII=') index.append('recycle-normal-trans') catalog['recycle-normal-trans'] = recycle_normal_trans #---------------------------------------------------------------------- reset_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAS5JREFUOI3N1CtLBFEUwPHf+kIUQQyC0TRdu0EMo8IuGFTE' b'R7KYBoNfQTFNMfkIIohFEBHmG+gHEGGCiGIQLEaxjMFZcHVnd4UNnnQf5/75n3Mvt5RlmXZE' b'R1so7QR1FW0EUTKIOaxhDBnucIKzNA5fv+eX6vUoiJJRHKIPR7jAB6axmQssp3F4WwjKTc5x' b'n8bheoHtLqZQTuPwmfo9mkNvEQTSONzKh/PVtRpQECUV7OCtCPIt9lCuC8I+hvDSAugSQXXS' b'lZtkOMAtJtAfRMkxVtI4LBWAetFZY5Qnj6IHTxjHcAMIVPBQA8pjBtd4xw0Wm5S2kZeHH9cf' b'REk3PpqYCKJkG0uYTOPw/heoyeERzCLCABbSOLypa9QAsurrWTziCqdVkz+BWon/9418AsPf' b'YZ6+4IYfAAAAAElFTkSuQmCC') index.append('reset-hover-trans') catalog['reset-hover-trans'] = reset_hover_trans #---------------------------------------------------------------------- reset_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAARBJREFUOI3NlD0vREEUhp9dkUg0q0CIRrR6foJotCKiWKIQ' b'GrVe4i9sovAXJCoRknvtFrJRbKnRkGxJoxCPZq5c3LluYiNO8hZnPp6ZOefN1FQGEfWBUP4K' b'1ACawBXwDDwBHWAXGP+2Wi3SrHqhdtRtdVIdU9fUG/VWnc/vKYI0AqQVOQT1SO2qM2Wgpnpd' b'AsnUVfdjoBW1r55VAG2qlzFQX31VjyuAJtTHLK8DpGkq0AJ6oQejwEkYj8UIMJQltZyzz8Pk' b'NPAG3AFLJaA9YB1YgM8+WgbawEvwy2oJBGAHOP3Ivrx7OEkSK9TnUL1X58raH9OUuqX2AmTx' b'J0MWaUN9UNvqQf4mmfLF/lX8v2/kHRYA4/PCNOsgAAAAAElFTkSuQmCC') index.append('reset-normal-trans') catalog['reset-normal-trans'] = reset_normal_trans #---------------------------------------------------------------------- save_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAOJJREFUOI3Nk0sKwjAQhr+K+FiJG7eCiFlVr9Hb2KP0NvYa' b'vhYF9+4EQSiKOi5MZIiNXejCH4b+mQlf0hkSiQi/UOMnlL8ENfXCpPnS2hgQG1vAAG2AIkui' b'SpKIvGIyXyyVF68mVXkX/q8N9Rle7eZuZNL8bdRNb91Sfm3S/AD0LXStaqs6UNeZIktm/mal' b'cR3o4oxt/FTVTkBZZMlAHxgC7ZXv+RNSU935IL/Zx4B3iu23rANpTStyUaj2CXStyJ1DmyP9' b'+k2a34ANMOLZvxZwt1ECPZ6jN0WWdIKgb/R/r/8BedRr5zE/61EAAAAASUVORK5CYII=') index.append('save-hover-trans') catalog['save-hover-trans'] = save_hover_trans #---------------------------------------------------------------------- save_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAMBJREFUOI3Vk8sKwkAMRc+IKCLiJ/gDPv7/M6q4cy+4EKWI' b'r7bjwhRDmrYgXeiFkExu5xCmMyHGSBfqdUL5C1AiUQA5kMn6BkQJV32nt5IcgaD65dr23Ylm' b'ZqNWLjk4XgU0UPUGOMqmAtgqb21Bwdwjd2xHF2DcNNFD1QmfA45AChzEG1myBe1VPZXpypgo' b'f9cGOtfUpRaSr20graXTC3VeEyhzeve6jy1ozvuQUwHlwFMAJ2AofgVof//X+r3X/wKamjad' b'ZgBOvwAAAABJRU5ErkJggg==') index.append('save-normal-trans') catalog['save-normal-trans'] = save_normal_trans #---------------------------------------------------------------------- show_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAPJJREFUOI3d07FOAkEQxvEfxJewxniJJTX0W1gTeQGoSI6C' b'p7BgEyt4AYy1xfVYU5KsgdrHwGaL8zgbpTBOM5Pv2/lnsrPbOZ1OLhHdi1D+N+jqO6Moq9Yt' b'pBg6bXqnubWirLbo4RpDHLPVwxYfOKYYhvW+bgPyiAFesEgxvGGEUa4X2Rvks+cTFWU1wQrr' b'FMO0ocE0xbDO+gqTulaf6CnnWYvWrGdNrQ66y3nfojXrfVP7ctlFWT1ggx3eUwzjoqz6kGLY' b'FWW1wS36GKcYnltBGbbEDe4xxyFbN1jiFYcUw7zedwaqAX/3jn4af++vXQz0CY6rXIXiqefL' b'AAAAAElFTkSuQmCC') index.append('show-hover-trans') catalog['show-hover-trans'] = show_hover_trans #---------------------------------------------------------------------- show_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAM1JREFUOI3dk7ENwkAMRV8QSnagpqKkoQpzZAJaGrZIkzYT' b'wABMABVF2lTU7ACNKfhE1nGJhJQCYcmK9fztO/mcxMwYwyajdPnvRtOBXN8rJDEYu9EZuCnO' b'gZk8F7tJExxr5r20l1VmthPbyhGrpCl9rW+ykaCOMFP85nXIEreQdyAFMuARMMQyxalyHfMz' b'WujbRlgYtx8smFGhKzdmthdbyhFrpCl8bfj8B2AFzIECuABX5dZiR+AkbWd+RqF9tUdDCxkt' b'6LPf+9dGa/QE+lnROoR24cQAAAAASUVORK5CYII=') index.append('show-normal-trans') catalog['show-normal-trans'] = show_normal_trans #---------------------------------------------------------------------- time_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAU1JREFUOI3N1LFuE0EQxvGfI2RRpTJFXIR0l45Q8A5bIb8C' b'Sh3psITSxRKRLFGYHA2uEI+A6e4t4pRXxRIgF2mIQkFEYYps0HG+s4XkgmlW2pn573wzu9ta' b'LBY2YVsboWwS9GCVM0nzA+zhE54WWZg2xbbqepSkeQ8jdDBHghtcoV9k4XM1Z0lakuYDvMcb' b'dIos7EfXDoYYx5jmimIlY4QiCxcNchf4jhdFFiZNFY0wWAUpstCKlb2tlRYb+wgf10DgHbox' b'B39P7TF+FFn4VSNFCQK3cT1Hqyptgp37xHurAMp7X9FbkoYD/ES7LrF8QJLm2+7aMFsCxQbP' b'cVgF1cCO8K08lOrUXmKYpPmzNbBXMfaPLd3sJM1PkOIUH3AdAW0co4+zIguvV4Ii7DnO0MVD' b'XGIXX5DWPZFaUAn4BFN305k1XdS1oH+x/+9j+w1LuImmmtb2nQAAAABJRU5ErkJggg==') index.append('time-hover-trans') catalog['time-hover-trans'] = time_hover_trans #---------------------------------------------------------------------- time_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAQRJREFUOI3dlL1KxFAQRk/ETSNsZ+0jbBpfYzfvIIithZW4' b'+4i2NhsFQTv/EFxROBY7K5eQmygsFg4MN0wyZz5mJrdQ2YbtbIXyl6AKqAHjOW9ql9fqUn1W' b'r1zbS8RmXTldkIV6px6ro4ip7qlH8W4xBKrVe3WSUbqBPsa3WdAylPRBUM/UJgeqog+jAQhq' b'qa4iB5XdpO8HwCvw0Z5HnEUSe4/z8jveqmircpeajd+mE0z3qAJWQNmxJUWiDGAM7AM3uT1q' b'1JMfNPs8BpOd2kx9UA8HYE/qtA+EOg/YqTpOVJTqhesdmrfzclWnIf0tQI36qV77i18k9UmA' b'ZvZvO4X/9mL7Am+xjftg54OyAAAAAElFTkSuQmCC') index.append('time-normal-trans') catalog['time-normal-trans'] = time_normal_trans #---------------------------------------------------------------------- up_24 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAA+JJREFUSImllcuLXEUUh79zTt3bPa+O6czkMZOYSBIxZiFm' b'IhpBcNvqXsmYlwkI5n/Qjf4Jioq2SxFEBMG1iyjB4EaJmokz3e1CHARJz0w/594qF/f2zCQ9' b'j4AFRd0q6v6+8zun6l7hA3ZvAczswnMz59679dWP7/Y/63/8EG8BYLy8yw4PojL32rHXP312' b'9IXy6ONjLy0md5f8bf/T/wd4iF08d/6xC9UpmYl/bd5hqnhAD549VKn1Hg6yPcBDwRXmLhy/' b'Uh0Pk/Fiqw6RsJKsUHZTemh2ulLv/7ErZGtALj53/HK1mJbixXYdHKR4fPCs9Fcpu0mdnp2p' b'1PsLO0KGAZvEC34irrUbiAmpT0mDJ+AJQKu/yt5on86cnanUe9tD7gd4KLri3KUTV6uRH49r' b'rQaokIYEj8eHFI8niCcItNZWKUeTenj2SKXeXljyvw1DNgAD8ZPXqlE6Fi+2G2CQhiwtHo8X' b'D5JtFwFVoZ12mCzs10dnj1RqKwtL/vf7IRkgF7988lo1TsfjWruBmRKAgCcJCWt+ja7v0k27' b'9HyPnu/R9z08Kc1ek73FfXrk6cOVRrO25Oc3IML7MGqjcxdPXK0W0om41mmQSMpq0qLZa7K8' b'tkw7aZOEBBRMFRPDTHGqiCiigopwYHw/lqTpzQ9vXO9/2/8IwNyr7vrF01c+OTpx3Jb4hzDi' b'ude/x+K/dRaW67SSNh6PmuLUcOaInCMyh5qxf2SS6dJByhN7iGLHdHlGy0+VX2ncq7XD3XDT' b'pV+mB76IPq8mPgkqEhKfdvecLj3/xLlnzvz1598UoxinbpO4rT+Lwr7xR7j19Y2fW3faPxBw' b'IXgQLHTCMZQpF74Jby/TvK/yRSm8oy/KGVPF1DAzTI3IGZE5nGWjqKCxsPLL6ved79pvbXFK' b'Yze0dBqkImhXcGKYKk6UyAaRZ+KROcw0G51GW90BoO8eFOdNwCHidT1ysywlLheOzBG5iMiU' b'2CLw28gDG4AngWtAALqgIuggRZo5ifLo47zIUZTNCdsDFIBTwBsb4qQgKvmRzI7jADQocOwc' b'sYsywI4OTgEXyTb189UUFEFF110407wbkW5KldmODhzn88h7+UoAEhARRAQbpEos63mhB/fB' b'yW4O0kxwvYXBXDDNxLN65CnLuzPL7ocZsiNg7YGVkKVIHtyZOxLJPgsqG8Cdmhuy50G86NjI' b'GKV+ifF4lKKLKUQxxThmJC5QjGKKUYGRYoHSSAlEtqU4VoYdrPZWO/Xb8+1C6sVLh570SDA6' b'oixvOlXOHPNunm632yczPVRuobAFNjDBGkeBAuS/sO2bYqyQUoOhhPMf3TF+Z01qI8AAAAAA' b'SUVORK5CYII=') index.append('up_24') catalog['up_24'] = up_24 #---------------------------------------------------------------------- vu_metre2 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAANoAAAAFCAIAAACsF2V2AAAAA3NCSVQICAjb4U/gAAABAklE' b'QVRIiWNgGAWjYNAARgYGBtdt7ljl/nz9czjhoONqZ6yyn+99vtp32WKKFVbZ95ff3V1616TD' b'FKvssz1P3196r12kg1X20YaH3199V0/TwCp7c9YNTjFOuQB5rLJX+64I6glKuUhjlT1TcVo5' b'WllQVwir7ImcY9pFurxKvFhl94futV1gz8LNglV2t9dOPMF4IueYzXw7rLIUBqPUmTfFaZpY' b'ZTfsePLyzff0GFWssjOX3BYX4QzwkMEq2zvruuErfncJCayyRRcuxivI6wsIYJVNO3O2XENd' b'mYcHq6z/kaOP//zBqpORgYEJq55RMAoGBIwmx1EwiMBochwFgwgAAH0VYAhA4kYpAAAAAElF' b'TkSuQmCC') index.append('vu-metre2') catalog['vu-metre2'] = vu_metre2 #---------------------------------------------------------------------- vu_metre_dark2 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAANoAAAAFCAIAAACsF2V2AAAAA3NCSVQICAjb4U/gAAAACXBI' b'WXMAAArwAAAK8AFCrDSYAAAA+ElEQVRIiWNgGAWjYNAARgYGBgsvDqxyf38znD/w08SVHavs' b't0//7176rWvDhlX207t/T27/0TLHLvv6yd9P7/4p67FilX3x4O/Pb//ltViwyj689oedi1FC' b'gRmr7N1Lv/mEmERlsMteO/lLRpWFT4gJq+zlI7+U9Vi5+Bixyp7Z/dPQgZ0Zu5MZTmz7gScY' b'Lx35aehIk2D8+eq/viZ22ftP/nz//k9LFbvstdu/ODmZFGWwB/LF67+k3zMrsmCX3ffjhw4r' b'qxgz9kDe+f27OTu7ABP2QF737VurlAwnNtmcxw+x6xkFo2BAwGhyHAWDCIwmx1EwiAAAHAVf' b'CAHlJsIAAAAASUVORK5CYII=') index.append('vu-metre-dark2') catalog['vu-metre-dark2'] = vu_metre_dark2 #---------------------------------------------------------------------- waves_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAYAAACaq43EAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAZxJREFUSInNlT1IHEEYhp87EpNIOKuAmCIphCljJ2hh0sgU' b'SSGmCghR4abMVBb+gGJhIZKpggMqKXIoVukyKYQTSZFGbMfmGgsLC5sDC5O1cE6WdfVEbnJ5' b'YWH3fXfm+fh2dqaQJAntULEt1P8GLLQrCe0m/gX4Qea5CxgGNkIhA8AYcOKNnGslONvqIvAk' b'QPuBbeAX0CO0WxPaPY4FJkB7gWlg1Bv5zRs5CTwD3kYFA1PAT2/k75S3ALwX2nXHAncDj4BK' b'2vRG7gNHwIdWgAvpDURo9wKoAXXgzw2FdoY8b+cpAiPeyJ1m4OyqBvgBvPNG/s0bILRbBmre' b'yC852RrwvBm0UWFW5zdBg1aApewKF9oNAR1c/gn3At8qb+Qx8BlYFdo9DNCnwCKw7o08iwIO' b'8HmgBMwI7QaBLWDPG7l71zmy37gOHN5xrAJmAQ1UvJGbV4FSX4GXqXdfAQfW2jcNoxDjWFRK' b'DQHVjP3aWnvVkSinUwB8T1nVNDQaOOgjcBru57NhlFY3pJT6BPRZa8evhUmSRL3K5XJXnn8B' b'rJjPsTlXkigAAAAASUVORK5CYII=') index.append('waves-hover-trans') catalog['waves-hover-trans'] = waves_hover_trans #---------------------------------------------------------------------- waves_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAYAAACaq43EAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAWJJREFUSInN1TFIHEEYxfGfR2JUQqwE0UIL+6QLmEJsUiVF' b'iJVVYuC21CYWanFiYSFiZzJgSIpIxMpOLAJKsLAJ1jZpLAQtbAQLdVPcGpZ1zzvE5fJgYea9' b'mf0P7H7ftMRxrBkqNYX6P4GfYKwZ4E68TM0H8QlzRYNLaE/Gz7GOXfRgBW1Fga81gCm8xXd8' b'QBdeFQ2exBb2Ut4sRtBdFLgbj7Ca8X/jEKP3AW7JNJA+/MEZLnPWl9CR5Hmdp4Q3+FkP/CDH' b'28RrXNXYs5AcbjknW0FvPej1CbO6uAUKi5h38w8fQqtqJdwJXE9HWMJnPEy8x6q1/gXnRYGh' b'otrlpvECa/iFnUZfkP3GZzhocG+EGUyoVsCPf0EUfUN/au1T7IcQhmuBT/CxQfAxxmtkX7Gd' b'8SrpSSG3UwhhBxspazvxigUneofTZFzJhtkGcq+Komgcz0II72+EcRwX+pTL5c48/y9jqZ5n' b'LqtqxQAAAABJRU5ErkJggg==') index.append('waves-normal-trans') catalog['waves-normal-trans'] = waves_normal_trans #---------------------------------------------------------------------- xfade_linear = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAPRJREFUKJF10kFOW0EQhOF5EXcKUiAbFIgwthITg/DRvMky' b'V2ABCAmMiHKNiGPUl808NB6/9GZ61P1PVbemYIOTUgPv55gnGcslyWf8LDhLcofvPdznSWa4' b'w2IsLvCEby3QPTDHFqvSBi7w3Co3tRn+4HrSSoW3WDb2zvGKddM3TMHzOscxPuGhVUqyC3Xw' b'If4mecNRr4TyoZ+nxkEpxTAMKaUM/+nZW/kS91X1Y81/7PV2Fpd1OfNuYS+42ltOvVxW6GLC' b'0SzJb9zsFJJc4hFfW0vtVxvhJOsRmuM2yenUzBNf7hGrgk2SL31jknfF7qFz/PoHNNxNwh8F' b'QxsAAAAASUVORK5CYII=') index.append('xfade-linear') catalog['xfade-linear'] = xfade_linear #---------------------------------------------------------------------- xfade_power = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAUpJREFUKJFtkrFKnUEQhc+st7ERBIV4xYiND3BRCxEb7RS0' b'CiqCPoIggVT2liE3gZTqxVIE0auFFmlinSaVj5B0otX5bHYvvz//NLNn58zMmZ0N298jogP8' b'i4jfwEVK6a8k2VZKScCkpE/AqqSxiHiUJAEJmAO6wAOwp2zABtAHesAy0JKkUM2AFUkHwK+I' b'eJG0JuksIs7fEW3XcwV8tP0K/AemG+JKKaUBqAQWIuKPpCdJo9UESYqI91WygiXg3vY8sAPc' b'AuON6oDIftj2FXBYiX0DftS5si3bJfELcAqkCnHC9rXt3YwHs7WyXwRugE65LyRgM8ufzXio' b'dA3bJ8BRUQEMlGTyV9vd+oz7ti9tj5RCDfN/APq2t0q3Gdt3wHp9LcXKawLbtvu22wEcS0oR' b'8bm6mrKr8l+rkoFn2f4JtGvSG88ZT9nuvQEC6UR8F3lSlgAAAABJRU5ErkJggg==') index.append('xfade-power') catalog['xfade-power'] = xfade_power #---------------------------------------------------------------------- xfade_sigmoid = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAATdJREFUKJF1kj1LlmEYho/rDmloUXBpEISGCNwskNbo8wdE' b'BjooCP2Clug3RENNUbRHS0tTGGri4uRgv6ChKWhoOY+Gnlef9zbv7brOj+s84S71tXoNOKyq' b'91V1CKCWWq21DPMKsAksAUcMywX1ufpVfUj3kmyou+pTda7HUe8n2UvyYCR6PIhunRF07uvq' b'J3VGXUyyk+R2x6HGQ2ttcvktsANcAVJVz4Y9VXXqoJ6JrG6rB+p8hwHQgGmXf+AecBn4XlU/' b'x4IJt/XZB/AX8Af4cV6qNnX+tOMCcBG4PiH2qU6ido6r6i7wW71zDmf6qTeTbKtXk9xVP6uz' b'/6s0Ft1L8k3dGu1eqe/USz35gnojycvhy611+Jz6Rv2QZPmks/oCWAT2gY9VdTwIJv2jzgBP' b'1EdVVeqXv48HAD7In3ZkAAAAAElFTkSuQmCC') index.append('xfade-sigmoid') catalog['xfade-sigmoid'] = xfade_sigmoid #---------------------------------------------------------------------- zoom_click_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAbBJREFUOI2t1M+LTXEYx/HXHQwR0fiRMuqcZMFi1haEmpyy' b'HBYaFBt2ZHYWVlZqVjazIdxsNMnyy4b8KJv5C/A9kRoUShYucS3Ol7md7j1TzKdOT98f532e' b'5+lznla327WUGlpSGpb32wxZvgsXcADr04fn0cZMUcbPg4Ct3pJDlg/jLC5iU9r+c6GV4iuc' b'Lsr4tB+wXvIkphPsLa5gIj038R070A5ZPtaYYcjyUTzBdsxhoijjm97LIcsP4Rq2IhRlPNyU' b'4TFswzscqcOgKON9TKnacDAlMRC4L8W5ooyv+5WTdA+fsAL7m4C707rTAFOUsYMPabmzCfgt' b'xZEmYNKGFOebgHdSHAtZvnYQKWT5ODaq+vioCdjGD5WRryZP1mGjmMUwnuHFQGBRxpe4hJ84' b'gQchy/ck0EjI8vN4iHXplS0WzP9XrfpwCFl+S2WhoVRWByst/ClfsSadP8bJXov1Gw5ncFRl' b'7l8JBh9xF+M4hy/Yi9mQ5asHZljLdhU2o1OU8X3tbBI3sAzPcbwoY2wELqaQ5VO4nKCnijLe' b'/q95WJRxWtWi64gsUvK/aMkn9m+VAIbEZ1QSYQAAAABJRU5ErkJggg==') index.append('zoom-click-trans') catalog['zoom-click-trans'] = zoom_click_trans #---------------------------------------------------------------------- zoom_hover_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAAWdJREFUOI211DFolDEUAODvtCgOpZU6CZ0zFtSlQ8VFyHaL' b'g6IUnboGXJwUB8FFSBHHFhcFwcFu0UVwcimcblkK7ejUSeniOdw/nOf959meb8wjH3nJy+v0' b'+32zjFMz1f4HONeWCKl0cRcL6OMAmzXH3iSwM3qHIZVFbOMyXuNzk+riFp7WHB//ywkfYhlr' b'NceDofWdkMobbIVUejXHnXHgb3cYUlnBTdwYwUDN8T3uY6vthKOP0sVuzXG/bQPe4WdI5eo0' b'YAdHEzA1xyN8w+I0ICxNAps4j8NpwB5WQirzbVJI5Tou4MtfweblengeUjkzBlvGW3zV0sPj' b'Sr6HNXwIqaw20FJIJeFjA11q8hdHN//R2A2wgEdYxzmcxT428QMvGvgT1odbbCw4KUIqc9jA' b'E8xjF9dqjt+PBQ7Bt/ESpw2+552a496xp03N8RUeGPTtFaxywvFVc3xmUP429jhByW0x8wH7' b'C158c2POAaS8AAAAAElFTkSuQmCC') index.append('zoom-hover-trans') catalog['zoom-hover-trans'] = zoom_hover_trans #---------------------------------------------------------------------- zoom_normal_trans = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlw' b'SFlzAAAK8AAACvABQqw0mAAAATNJREFUOI3NlL1KQ0EQRs+VYCFIlFgJRvAFUmthJ5a3tEhj' b'YyWI4AP4BArmCXwC8QnESkEJxM5CBUUsNEUsRPzLscjaxJvNhURwYJr92DOz3w6TqAwzRoZK' b'+wtgIaKlwCpQBATugD2gEQNmdTgBHAA14DJAagF6CmxHW1S7c1c9V8sZ2rJ6r6YZGuovYEV9' b'UGd7XVBX1GYvvfvJKVAHbiOPOgTawGIeDxPgLepRR3+k43VfIECpDxBgEmjlATaACjAegS0B' b'U8BFppph7JG6r45maDPqc5iCUp5fJvzwtXqszoezkrqpXqkv6pdaV6fzAFGLYR6bAfAZimyo' b'a+q72g5Fy3mAsSyo62ordHqmjg0C/Mmq+hE6PVHnBgWibqmvwYKqGt02eWIHeAIWgBuAxP++' b'sb8B+DgaoxP3ukQAAAAASUVORK5CYII=') index.append('zoom-normal-trans') catalog['zoom-normal-trans'] = zoom_normal_trans cecilia5-5.4.1/Resources/menubar.py000066400000000000000000000242351372272363700171760ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx, os from .constants import * import Resources.CeciliaLib as CeciliaLib class InterfaceMenuBar(wx.MenuBar): def __init__(self, frame, mainFrame=None): wx.MenuBar.__init__(self, wx.MB_DOCKABLE) self.frame = frame if mainFrame: self.mainFrame = mainFrame else: self.mainFrame = CeciliaLib.getVar("mainFrame") inMainFrame = False if frame == mainFrame: inMainFrame = True # File Menu self.fileMenu = wx.Menu() self.fileMenu.Append(ID_OPEN, 'Open...\tCtrl+O', kind=wx.ITEM_NORMAL) self.frame.Bind(wx.EVT_MENU, self.mainFrame.onOpen, id=ID_OPEN) self.fileMenu.Append(ID_OPEN_RANDOM, 'Open Random...\tShift+Ctrl+O', kind=wx.ITEM_NORMAL) self.frame.Bind(wx.EVT_MENU, self.mainFrame.onOpenRandom, id=ID_OPEN_RANDOM) ######## Implement the Open builtin menu ######### self.root, self.directories, self.files = CeciliaLib.buildFileTree() self.openBuiltinMenu = wx.Menu() subId1 = ID_OPEN_BUILTIN for dir in self.directories: menu = wx.Menu() self.openBuiltinMenu.AppendSubMenu(menu, dir) for f in self.files[dir]: menu.Append(subId1, f) self.frame.Bind(wx.EVT_MENU, self.mainFrame.onOpenBuiltin, id=subId1) subId1 += 1 prefPath = CeciliaLib.getVar("prefferedPath") if prefPath: for path in prefPath.split(';'): path = CeciliaLib.ensureNFD(path) if not os.path.isdir(path): continue menu = wx.Menu(os.path.split(path)[1]) self.openBuiltinMenu.AppendSubMenu(menu, os.path.split(path)[1]) files = os.listdir(path) for file in files: if os.path.isfile(os.path.join(path, file)): ok = False try: ext = file.rsplit('.')[1] if ext == FILE_EXTENSION: ok = True except: ok = False if ok: try: menu.Append(subId1, CeciliaLib.ensureNFD(file)) self.frame.Bind(wx.EVT_MENU, self.mainFrame.onOpenPrefModule, id=subId1) subId1 += 1 except: pass self.fileMenu.AppendSubMenu(self.openBuiltinMenu, 'Modules') self.openRecentMenu = wx.Menu() subId2 = ID_OPEN_RECENT recentFiles = [] if os.path.isfile(RECENT_FILE_PATH): f = open(RECENT_FILE_PATH, "r") for line in f.readlines(): try: recentFiles.append(line) except: pass f.close() if recentFiles: for file in recentFiles: try: self.openRecentMenu.Append(subId2, CeciliaLib.ensureNFD(file).replace("\n", "")) subId2 += 1 except: pass if subId2 > ID_OPEN_RECENT: for i in range(ID_OPEN_RECENT, subId2): self.frame.Bind(wx.EVT_MENU, self.mainFrame.openRecent, id=i) self.fileMenu.AppendSubMenu(self.openRecentMenu, 'Open Recent') self.fileMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) self.fileMenu.Append(ID_SAVEAS, 'Save Module As...\tShift+Ctrl+S') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onSaveAs, id=ID_SAVEAS) self.fileMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) self.fileMenu.Append(ID_OPEN_AS_TEXT, 'Open Module As Text\tCtrl+E') self.frame.Bind(wx.EVT_MENU, self.mainFrame.openModuleAsText, id=ID_OPEN_AS_TEXT) self.fileMenu.Append(ID_UPDATE_INTERFACE, 'Reload Module\tCtrl+R') self.frame.Bind(wx.EVT_MENU, self.mainFrame.reloadCurrentModule, id=ID_UPDATE_INTERFACE) if CeciliaLib.getVar("systemPlatform").startswith("linux") or CeciliaLib.getVar("systemPlatform") == 'win32': self.fileMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) pref_item = self.fileMenu.Append(wx.ID_PREFERENCES, 'Preferences...\tCtrl+,') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onPreferences, pref_item) if CeciliaLib.getVar("systemPlatform").startswith("linux") or CeciliaLib.getVar("systemPlatform") == 'win32': self.fileMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) quit_item = self.fileMenu.Append(wx.ID_EXIT, 'Quit\tCtrl+Q') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onQuit, quit_item) # Edit Menu self.editMenu = wx.Menu() if not inMainFrame: self.editMenu.Append(ID_UNDO, 'Undo\tCtrl+Z') self.frame.Bind(wx.EVT_MENU, self.frame.onUndo, id=ID_UNDO) self.editMenu.Append(ID_REDO, 'Redo\tShift+Ctrl+Z') self.frame.Bind(wx.EVT_MENU, self.frame.onRedo, id=ID_REDO) self.editMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) self.editMenu.Append(ID_COPY, 'Copy\tCtrl+C') self.frame.Bind(wx.EVT_MENU, self.frame.onCopy, id=ID_COPY) self.editMenu.Append(ID_PASTE, 'Paste\tCtrl+V') self.frame.Bind(wx.EVT_MENU, self.frame.onPaste, id=ID_PASTE) self.editMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) self.editMenu.Append(ID_SELECT_ALL, 'Select All Points\tCtrl+A') self.frame.Bind(wx.EVT_MENU, self.frame.onSelectAll, id=ID_SELECT_ALL) self.editMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) self.editMenu.Append(ID_REMEMBER, 'Remember Input Sound', kind=wx.ITEM_CHECK) self.editMenu.FindItemById(ID_REMEMBER).Check(CeciliaLib.getVar("rememberedSound")) self.frame.Bind(wx.EVT_MENU, self.mainFrame.onRememberInputSound, id=ID_REMEMBER) # Action Options Menu self.actionMenu = wx.Menu() self.actionMenu.Append(ID_PLAY_STOP, 'Play / Stop\tCtrl+.') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onShortPlayStop, id=ID_PLAY_STOP) self.actionMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) self.actionMenu.Append(ID_BOUNCE, 'Bounce to Disk\tCtrl+B') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onBounceToDisk, id=ID_BOUNCE) self.actionMenu.Append(ID_BATCH_PRESET, 'Batch Processing on Preset Sequence') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onBatchProcessing, id=ID_BATCH_PRESET) self.actionMenu.Append(ID_BATCH_FOLDER, 'Batch Processing on Sound Folder') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onBatchProcessing, id=ID_BATCH_FOLDER) self.actionMenu.Append(ID_USE_SOUND_DUR, 'Use Sound Duration on Folder Batch Processing', kind=wx.ITEM_CHECK) if CeciliaLib.getVar("useSoundDur") == 1: self.actionMenu.FindItemById(ID_USE_SOUND_DUR).Check(True) self.frame.Bind(wx.EVT_MENU, self.mainFrame.onUseSoundDuration, id=ID_USE_SOUND_DUR) self.actionMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) self.actionMenu.Append(ID_SHOW_SPECTRUM, 'Show Spectrum', kind=wx.ITEM_CHECK) self.frame.Bind(wx.EVT_MENU, self.mainFrame.onShowSpectrum, id=ID_SHOW_SPECTRUM) if CeciliaLib.getVar('showSpectrum'): self.actionMenu.FindItemById(ID_SHOW_SPECTRUM).Check(True) self.actionMenu.Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) self.actionMenu.Append(ID_USE_MIDI, 'Use MIDI', kind=wx.ITEM_CHECK) if CeciliaLib.getVar("useMidi") == 1: midiCheck = True else: midiCheck = False self.actionMenu.FindItemById(ID_USE_MIDI).Check(midiCheck) self.actionMenu.FindItemById(ID_USE_MIDI).Enable(False) self.frame.Bind(wx.EVT_MENU, self.mainFrame.onUseMidi, id=ID_USE_MIDI) windowMenu = wx.Menu() windowMenu.Append(ID_MARIO, 'Eh Oh Mario!\tShift+Ctrl+E', kind=wx.ITEM_CHECK) self.frame.Bind(wx.EVT_MENU, self.marioSwitch, id=ID_MARIO) helpMenu = wx.Menu() helpItem = helpMenu.Append(wx.ID_ABOUT, '&About %s %s' % (APP_NAME, APP_VERSION)) self.frame.Bind(wx.EVT_MENU, self.mainFrame.onHelpAbout, helpItem) infoItem = helpMenu.Append(ID_MODULE_INFO, 'Show Module Info\tCtrl+I') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onModuleAbout, infoItem) docItem = helpMenu.Append(ID_DOC_FRAME, 'Show API Documentation\tCtrl+D') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onDocFrame, docItem) graphItem = helpMenu.Append(ID_GRAPH_FRAME, 'Show Grapher Help\tCtrl+G') self.frame.Bind(wx.EVT_MENU, self.mainFrame.onGraphFrame, graphItem) self.Append(self.fileMenu, '&File') self.Append(self.editMenu, '&Edit') self.Append(self.actionMenu, '&Action') self.Append(windowMenu, '&Window') self.Append(helpMenu, '&Help') def spectrumSwitch(self, state): self.actionMenu.FindItemById(ID_SHOW_SPECTRUM).Check(state) def marioSwitch(self, evt): if evt.GetInt() == 1: self.FindItemById(ID_MARIO).Check(1) for slider in CeciliaLib.getVar("userSliders"): slider.slider.useMario = True slider.slider.Refresh() else: self.FindItemById(ID_MARIO).Check(0) for slider in CeciliaLib.getVar("userSliders"): slider.slider.useMario = False slider.slider.Refresh() cecilia5-5.4.1/Resources/modules/000077500000000000000000000000001372272363700166355ustar00rootroot00000000000000cecilia5-5.4.1/Resources/modules/Dynamics/000077500000000000000000000000001372272363700204045ustar00rootroot00000000000000cecilia5-5.4.1/Resources/modules/Dynamics/Degrade.c5000066400000000000000000000074031372272363700221740ustar00rootroot00000000000000class Module(BaseModule): """ "Sampling rate and bit depth degradation with optional mirror clipping" Description This module allows the user to degrade a sound with artificial resampling and quantization. This process emulates the artifacts caused by a poor sampling frequency or bit depth resolution. It optionally offers a simple mirror distortion, if the degradation is not enough! Sliders # Bit Depth : Resolution of the amplitude in bits # Sampling Rate Ratio : Ratio of the new sampling rate compared to the original one # Mirror Threshold : Clipping limits between -1 and 1 (signal is reflected around the thresholds) # Filter Freq : Center frequency of the filter # Filter Q : Q factor of the filter # Dry / Wet : Mix between the original signal and the degraded signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of filter # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Chords : Pitch interval between voices (chords), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.degr = Degrade(input=self.snd, bitdepth=self.bit, srscale=self.sr, mul=1) self.wrap = Mirror(self.degr, self.clip*-1, self.clip) self.biquad = Biquadx(self.wrap, freq=self.filter, q=self.filterq, type=self.filttype_index, stages=4, mul=0.7) self.deg = Interp(self.snd, self.biquad, self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self,index,value): if index == 0: self.out.interp= 0 elif index == 1: self.out.interp= 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd def filttype(self, index, value): self.biquad.type = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="bit", label="Bit Depth", min=1, max=32, init=8, rel="lin", unit="bit", col="purple1"), cslider(name="sr", label="Sample Rate Ratio", min=0.01, max=1, init=0.25, rel="log", unit="x", col="purple2"), cslider(name="clip", label="Mirror Threshold", min=0.01, max=1, init=0.8, rel="lin", unit="x", col="purple3"), cslider(name="filter", label="Filter Freq", min=30, max=20000, init=15000, rel="log", unit="Hz", col="green1"), cslider(name="filterq", label="Filter Q", min=0.5, max=10, init=0.707, rel="log", unit="Q", col="green2"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="filttype", label="Filter Type", init="Lowpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Dynamics/Distortion.c5000066400000000000000000000102011372272363700227650ustar00rootroot00000000000000class Module(BaseModule): """ "Arctangent distortion module with pre and post filters" Description This module applies an arctangent distortion with control on the amount of drive and pre/post filtering. Sliders # Pre Filter Freq : Center frequency of the filter applied before distortion # Pre Filter Q : Q factor of the filter applied before distortion # Pre Gain : Gain control applied before the distortion # Drive : Amount of distortion applied on the signal # Post Filter Freq : Center frequency of the filter applied after distortion # Post Filter Q : Q factor of the filter applied after distortion # Dry / Wet : Mix between the original signal and the degraded signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Pre Filter Type : Type of filter used before distortion # Post Filter Type : Type of filter used after distortion # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.snd_filt = Biquadx(self.snd, freq=self.prefiltf, q=self.prefiltq, type=self.prefilttype_index, stages=2) self.input = Sig(self.snd_filt, mul=DBToA(self.pregain)) self.disto = Disto(self.input, drive=self.drv, slope=0, mul=.2) self.disto_filt = Biquadx(self.disto, freq=self.cut, q=self.q, stages=2, type=self.postfilttype_index) self.deg = Interp(self.snd, self.disto_filt, self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index == 1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd def prefilttype(self, index, value): self.snd_filt.type = index def postfilttype(self, index, value): self.out.type = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="prefiltf", label="Pre Filter Freq", min=100, max=18000, init=250, rel="log", unit="Hz", col="green1"), cslider(name="prefiltq", label="Pre Filter Q", min=.5, max=10, init=0.707, rel="log", col="green2"), cslider(name="pregain", label="Pre Gain", min=-48, max=18, init=0, rel="lin", col="blue2"), cslider(name="drv", label="Drive", min=0.5, max=1, init=.9, rel="lin", col="purple1"), cslider(name="cut", label="Post Filter Freq", min=100, max=18000, init=5000, rel="log", col="green3"), cslider(name="q", label="Post Filter Q", min=.5, max=10, init=0.707, rel="log", col="green4"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="prefilttype", label="Pre Filter Type", init="Highpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="postfilttype", label="Post Filter Type", init="Lowpass", col="green3", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Dynamics/DynamicsProcessor.c5000066400000000000000000000076421372272363700243150ustar00rootroot00000000000000class Module(BaseModule): """ "Dynamic compression and gate module" Description This module can be used to adjust the dynamic range of a signal by applying a compressor followed by a gate. Sliders # Input Gain : Adjust the amount of signal sent to the processing chain # Comp Thresh : dB value at which the compressor becomes active # Comp Rise Time : Time taken by the compressor to reach compression ratio # Comp Fall Time : Time taken by the compressor to reach uncompressed state # Comp Knee : Steepness of the compression curve # Gate Thresh : dB value at which the gate becomes active # Gate Rise Time : Time taken to open the gate # Gate Fall Time : Time taken to close the gate # Output Gain : Makeup gain applied after the processing chain Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Compression Ratio : Ratio between the compressed signal and the uncompressed signal # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.crank = Sig(self.snd, mul=DBToA(self.inputgain)) self.comp = Compress(input=self.crank, thresh=self.compthresh, ratio=1, risetime=self.comprise, falltime=self.compfall, lookahead=5, knee=self.compknee.get(), outputAmp=False, mul=1) self.gate = Gate(input=self.comp , thresh=self.gatethresh, risetime=self.gaterise, falltime=self.gatefall, lookahead=5.00, outputAmp=False, mul=DBToA(self.outputgain)) self.out = self.gate*self.env #INIT self.compratio(self.compratio_index, self.compratio_value) def compratio(self, index, value): ratioList = [0.25,0.5,1,2,3,5,8,13,21,34,55,100] self.comp.ratio = ratioList[index] def compknee_up(self, value): self.comp.knee = value Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="inputgain", label="Input Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue1"), cslider(name="compthresh", label="Comp Thresh", min=-60, max=-0.1, init=-20, rel="lin", unit="dB", col="orange1"), cslider(name="comprise", label="Comp Rise Time", min=0.01, max=1, init=0.01, rel="lin", unit="sec", col="orange2"), cslider(name="compfall", label="Comp Fall Time", min=0.01, max=1, init=0.1, rel="lin", unit="sec", col="orange3"), cslider(name="gatethresh", label="Gate Thresh", min=-80, max=-0.1, init=-60, rel="lin", unit="dB", col="green1"), cslider(name="gaterise", label="Gate Rise Time", min=0.01, max=1, init=0.01, rel="lin", unit="x", col="green2"), cslider(name="gatefall", label="Gate Fall Time", min=0.01, max=1, init=0.1, rel="lin", unit="x", col="green3"), cslider(name="compknee", label="Comp Knee", min=0, max=1, init=0.5, rel="lin", unit="x", up=True, col="orange4"), cslider(name="outputgain", label="Output Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue1"), cpopup(name="compratio", label="Comp Ratio", init="1:1", col="orange1", value=["0.25:1", "0.5:1", "1:1", "2:1", "3:1", "5:1", "8:1", "13:1", "21:1", "34:1", "55:1", "100:1"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Dynamics/FeedbackLooper.c5000066400000000000000000000055131372272363700235060ustar00rootroot00000000000000class Module(BaseModule): """ "Frequency self-modulated sound looper" Description This module loads a sound in a table and apply a frequency self-modulated playback of the content. A Frequency self-modulation occurs when the output sound of the playback is used to modulate the reading pointer speed. That produces new harmonics in a way similar to waveshaping distortion. Sliders # Transposition : Transposition, in cents, of the input sound # Feedback : Amount of self-modulation in sound playback # Filter Frequency : Frequency, in Hertz, of the filter # Filter Q : Q of the filter (inverse of the bandwidth) # Dry / Wet : Mix between the original signal and the degraded signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of the filter # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Chords : Pitch interval between voices (chords), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addFilein("snd") self.trfactor = CentsToTranspo(self.transpo, mul=self.polyphony_spread) self.freq = Sig(self.trfactor, mul=self.snd.getRate()) self.dry = Osc(self.snd, self.freq, mul=self.polyphony_scaling * 0.5) self.dsp = OscLoop(self.snd, self.freq, self.feed*0.0002, mul=self.polyphony_scaling * 0.5) self.mix = self.dsp.mix(self.nchnls) self.filt = Biquad(self.mix, freq=self.filt_f, q=self.filt_q, type=self.filt_t_index) self.out = Interp(self.dry, self.filt, self.drywet, mul=self.env) def filt_t(self, index, value): self.filt.type = index Interface = [ cfilein(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="transpo", label="Transposition", min=-4800, max=4800, init=0, unit="cnts", col="red1"), cslider(name="feed", label="Feedback", min=0, max=1, init=0.25, unit="x", col="purple1"), cslider(name="filt_f", label="Filter Frequency", min=20, max=18000, init=10000, rel="log", unit="Hz", col="green1"), cslider(name="filt_q", label="Filter Q", min=0.5, max=25, init=1, rel="log", unit="x", col="green2"), cpopup(name="filt_t", label="Filter Type", init="Lowpass", value=["Lowpass", "Highpass", "Bandpass", "Bandreject"], col="green1"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpoly() ] cecilia5-5.4.1/Resources/modules/Dynamics/UpDistoRes.c5000066400000000000000000000114361372272363700227030ustar00rootroot00000000000000class Module(BaseModule): """ "Arctangent distortion module with upsampling and resonant lowpass filter" Description This module applies an arctangent distortion on an upsampled signal and pass the result through a 24dB/oct lowpass resonant filter. Sliders # Pre-Filter Freq : Center frequency of the filter applied before distortion # Pre-Filter Q : Q factor of the filter applied before distortion # Pre-Gain : Gain control applied before the distortion # Drive : Amount of distortion applied on the signal # Lowpass Freq : Cutoff frequency of the 24dB/oct lowpass filter applied after distortion # Lowpass Res : Resonance factor of the 24dB/oct lowpass filter applied after distortion # Dry / Wet : Mix between the original signal and the degraded signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Pre Filter Type : Type of filter used before distortion # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Upsampling : The resampling factor. The process will be applied with a virtual sampling rate of the current sampling rate times this factor. # Interpolation : Defines the FIR lowpass kernel length used for interpolation and decimation. The kernel length will be the upsampling factor times this value. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.snd_filt = Biquadx(self.snd, freq=self.prefiltf, q=self.prefiltq, type=self.prefilttype_index, stages=2) self.input = Sig(self.snd_filt, mul=DBToA(self.pregain)) self.drive = Sqrt(self.drv * 0.1 + 0.9) upfactor = int(self.factor_value) filtermode = int(self.fmode_value) server = self.input.getServer() server.beginResamplingBlock(upfactor) self.inputup = Resample(self.input, mode=filtermode) self.drvup = Resample(self.drive, mode=1) self.disto = Disto(self.inputup, drive=self.drvup, mul=0.5) server.endResamplingBlock() self.sigdown = Resample(self.disto, mode=filtermode) self.distof = MoogLP(self.sigdown, freq=self.cut, res=self.res) self.deg = Interp(self.snd, self.distof, self.drywet, mul=self.env) self.osc = Sine(10000, mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def prefilttype(self, index, value): self.snd_filt.type = index def balance(self,index,value): if index == 0: self.out.interp = 0 elif index == 1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="prefiltf", label="Pre-Filter Freq", min=100, max=18000, init=250, rel="log", unit="Hz", col="green1"), cslider(name="prefiltq", label="Pre-Filter Q", min=.5, max=10, init=0.707, rel="log", col="green2"), cslider(name="pregain", label="Pre-Gain", min=-48, max=18, init=0, rel="lin", col="blue2"), cslider(name="drv", label="Drive", min=0, max=1, init=.75, rel="lin", col="purple1"), cslider(name="cut", label="Lowpass Freq", min=100, max=15000, init=5000, rel="log", col="green3"), cslider(name="res", label="Lowpass Res", min=0, max=1, init=0.5, col="green4"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="balance", label = "Balance", init= "Off", col="blue", value=["Off","Compress", "Source"]), cpopup(name="prefilttype", label="Pre-Filter Type", init="Highpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="factor", label = "Upsampling", init= "8", rate="i", col="grey", value=["2","4", "8", "16", "32"]), cpopup(name="fmode", label = "Interpolation", init= "32", rate="i", col="grey", value=["2","4", "8", "16", "32", "64", "128", "256"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Dynamics/WaveShaper.c5000066400000000000000000000045341372272363700227100ustar00rootroot00000000000000class Module(BaseModule): """ "Table lookup waveshaping module" Description This module applies a waveshaping-based distortion on the input sound. It allows the user to draw the transfert function on the screen. Sliders # Filter Freq : Center frequency of the post-process filter # Filter Q : Q factor of the post-process filter # Dry / Wet : Mix between the original signal and the degraded signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Transfer Function : Table used as transfert function for waveshaping Popups & Toggles # Filter Type : Type of the post-process filter # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.lookup = Lookup(self.function, self.snd) self.lookdc = DCBlock(self.lookup, mul=0.4) self.filt = Biquadx(self.lookdc, freq=self.cut, q=self.filterq, type=self.filttype_index, stages=3) self.out = Interp(self.snd, self.filt, self.drywet, mul=self.env) def filttype(self, index, value): self.out.type = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cgraph(name="function", label="Transfer Function", func=[(0,1),(0.5,1),(0.501,0),(1,0)], table=True, col="orange1"), cslider(name="cut", label="Filter Freq", min=100, max=18000, init=7000, rel="log", unit="Hz", col="green1"), cslider(name="filterq", label="Filter Q", min=0.5, max=10, init=0.707, rel="log", unit="Q", col="green2"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="filttype", label="Filter Type", init="Lowpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/000077500000000000000000000000001372272363700202455ustar00rootroot00000000000000cecilia5-5.4.1/Resources/modules/Filters/AMFMFilter.c5000066400000000000000000000115551372272363700223730ustar00rootroot00000000000000class Module(BaseModule): """ "AM/FM modulated filter" Description The input sound is filtered by a variable type modulated filter. Speed, depth and shape can be modified for both AM and FM modulators. Sliders # Filter Mean Freq : Mean frequency of the filter # Resonance : Q factor of the filter # AM Depth : Amplitude of the amplitude modulator # AM Freq : Speed, in Hz, of the amplitude modulator # FM Depth : Amplitude of the frequency modulator # FM Freq : Speed, in Hz, of the frequency modulator # Mod Sharpness : Sharpness of waveforms used as modulators # Dry / Wet : Mix between the original signal and the filtered signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of filter # AM Mod Type : Shape of the amplitude modulator # FM Mod Type : Shape of the frequency modulator # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.lfomodAM = LFO(freq=self.modfreqAM, sharp=self.modSharp, type=self.modtypeAM_index, mul=0.5, add=0.5) self.lfomodFM = LFO(freq=self.modfreqFM, sharp=self.modSharp, type=self.modtypeFM_index, mul=0.5, add=0.5) self.filt = Biquadx(input=self.snd, freq=self.centerfreq*(self.lfomodFM*(self.moddepthFM*2)+(1-self.moddepthFM))+50, q=self.filterq, type=self.filttype_index, stages=2, mul=0.7 *(self.lfomodAM*self.moddepthAM+(1-self.moddepthAM))) self.deg = Interp(self.snd, self.filt, self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def filttype(self, index, value): self.filt.type = index def modtypeFM(self, index, value): self.lfomodFM.type = (index - 1) % 8 def modtypeAM(self, index, value): self.lfomodAM.type = (index - 1) % 8 def balance(self,index,value): if index == 0: self.out.interp = 0 elif index == 1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="centerfreq", label="Filter Mean Freq", min=20, max=20000, init=2000, rel="log", unit="Hz", col="green1"), cslider(name="filterq", label="Resonance", min=0.5, max=10, init=0.707, rel="lin", unit="Q", col="green2"), cslider(name="moddepthAM", label="AM Depth", min=0.001, max=1, init=0.5, rel="lin", unit="x", col="red1", half=True), cslider(name="moddepthFM", label="FM Depth", min=0.001, max=1, init=0.85, rel="lin", unit="x", col="purple1", half=True), cslider(name="modfreqAM", label="AM Freq", min=0.01, max=2000, init=1, rel="log", unit="Hz", col="red2", half=True), cslider(name="modfreqFM", label="FM Freq", min=0.01, max=2000, init=10, rel="log", unit="Hz", col="purple2", half=True), cslider(name="modSharp", label="Mod Sharpness", min=0, max=1, init=0.5, rel="lin", unit="x", col="orange1"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="filttype", label="Filter Type", init="Bandpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="modtypeAM", label="AM Mod Type", init="Sine", col="red1", value=["Sine", "Saw Up", "Saw Down", "Square", "Triangle", "Pulse", "Bipolar Pulse", "SAH"]), cpopup(name="modtypeFM", label="FM Mod Type", init="Sine", col="purple1", value=["Sine", "Saw Up", "Saw Down", "Square", "Triangle", "Pulse", "Bipolar Pulse", "SAH"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/AutoModFilter.c5000066400000000000000000000137411372272363700232220ustar00rootroot00000000000000class Module(BaseModule): """ "Filter audo-modulated by an extracted feature of its input sound" Description This module implements a variable filter, whose frequency is driven by the outcome of an analysis on its input signal. Available analysis continuous amplitude, centroid, fundamental frequency and zero-crossing. The filter type can be lowpass, highpass, bandpass or bandstop. Sliders # Freq Range : Lowest and highest filter frequencies, corresponding to the lowest and highest values of the analyzer output. # Filter Q : Q factor (inverse of bandwidth) of the filter. # Num of Filters : Number of filters (with identical parameters) applied in serie. # Dry / Wet : Mix between the original and the filtered signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Analysis Type : Choose between the four possible analyzers (amplitude, centroid, pitch or zero-crossing). # Filter Type : Choose the type of the filter (lowpass, highpass, bandpass or bandstop). # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.ampFollow = Follower(self.snd, freq=20).stop() self.ampScale = Scale(self.ampFollow, outmin=self.frange[0], outmax=self.frange[1]).stop() self.centroidFollow = Centroid(self.snd, size=512).stop() self.centroidScale = Scale(self.centroidFollow, inmin=20, inmax=15000, outmin=self.frange[0], outmax=self.frange[1]).stop() self.pitchFollow = Yin(self.snd, tolerance=0.20, minfreq=40, maxfreq=2000, cutoff=1000, winsize=2048).stop() self.pitchScale = Scale(self.pitchFollow, inmin=40, inmax=2000, outmin=self.frange[0], outmax=self.frange[1]).stop() self.zcrossFollow = ZCross(self.snd, thresh=0.001).stop() self.zcrossScale = Scale(self.zcrossFollow, outmin=self.frange[0], outmax=self.frange[1]).stop() self.filterFreq = Clip(self.centroidScale, min=self.frange[0], max=self.frange[1]) self.filterFreqPort = Port(self.filterFreq, 0.02, 0.02) self.filter = Biquadx(self.snd, freq=self.filterFreqPort, q=self.q, type=self.filttype_index, stages=int(self.stages.get())) self.proc = Interp(self.snd, self.filter, self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.proc, self.osc, freq=10) self.out = Interp(self.proc, self.balanced) self.lastAnalysis = "" self.currentAnalysis = "" self.analysisLaterCall = CallAfter(self.analysisTurnoff, time=0.1) #INIT self.balance(self.balance_index, self.balance_value) self.analysis(self.analysis_index, self.analysis_value) def balance(self, index, value): if index == 0: self.out.interp = 0 elif index == 1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd def analysis(self, index, value): self.lastAnalysis = self.currentAnalysis self.currentAnalysis = value if index == 0: self.ampFollow.play() self.ampScale.play() self.filterFreq.setInput(self.ampScale, 0.05) elif index == 1: self.centroidFollow.play() self.centroidScale.play() self.filterFreq.setInput(self.centroidScale, 0.05) elif index == 2: self.pitchFollow.play() self.pitchScale.play() self.filterFreq.setInput(self.pitchScale, 0.05) elif index == 2: self.zcrossFollow.play() self.zcrossScale.play() self.filterFreq.setInput(self.zcrossScale, 0.05) self.analysisLaterCall.play() def analysisTurnoff(self): if self.lastAnalysis == self.currentAnalysis: return if self.lastAnalysis == "Amplitude": self.ampFollow.stop() self.ampScale.stop() elif self.lastAnalysis == "Centroid": self.centroidFollow.stop() self.centroidScale.stop() elif self.lastAnalysis == "Pitch": self.pitchFollow.stop() self.pitchScale.stop() elif self.lastAnalysis == "Zero-Crossing": self.zcrossFollow.stop() self.zcrossScale.stop() def filttype(self, index, value): self.filter.type = index def stages_up(self, value): self.filter.stages = value Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), crange(name='frange', label='Freq Range', min=20.0, max=18000.0, init=[100.0, 5000.0], rel='log', unit='Hz', col='green'), cslider(name="q", label="Filter Q", min=0.5, max=5, init=1, unit="Q", col="green2"), cslider(name="stages", label="# of Filters", min=1, max=4, init=2, unit="x", res="int", up=True), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="analysis", label="Analysis Type", init="Amplitude", value=["Amplitude", "Centroid", "Pitch", "Zero-Crossing"], col="orange"), cpopup(name="filttype", label="Filter Type", init="Lowpass", col="green3", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/Binaural.c5000066400000000000000000000074711372272363700222440ustar00rootroot00000000000000class Module(BaseModule): """ "Binaural 3D spatialization" Description This module provides a realtime 3D spatialization over two channels by the mean of combining VBAP and HRTF algorithms. VBAP is used to move the sound over a sixteen channels speaker setup without artifact and its result signals are then processed with Head-related Transfert Functions to mix them on a 3D sphere around a virtual head. This treatment is better perceived when listened with headphones! Sliders # Azimuth : Position of the sound on the horizontal plane, between -180 and 180 degrees. # Elevation : Position of the sound on the vertical plane, between 0 and 90 degrees. # Azimuth Span : Spreading of the sound on the horizontal plane. # Elevation Span : Spreading of the sound on the vertical plane. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Monofy Source : If checked, a multi-channel source is mixed down to a mono signal before the spatialization. Otherwise, the channels are spreaded in azimuth over 360 degrees and are spatialized independently. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.isMonofied = False self.filterMono = Binaural(self.snd.mix(), azimuth=self.azimuth, elevation=self.elevation, azispan=self.azispan, elespan=self.elespan, mul=0.7) self.mixedFilterMono = Mix(self.filterMono, voices=self.nchnls*2, mul=0.7) offset = 360.0 / self.nchnls self.scaledAzimuth = Sig(self.azimuth, add=[offset * i - 45. for i in range(self.nchnls)]) self.azimuthMulti = Wrap(self.scaledAzimuth, min=-180, max=180) self.filterMulti = Binaural(self.snd, azimuth=self.azimuthMulti, elevation=self.elevation, azispan=self.azispan, elespan=self.elespan, mul=0.7) self.interp = Interp(self.mixedFilterMono, self.filterMulti, interp=1) self.out = Mix(self.interp, voices=self.nchnls, mul=self.env) self.monofy(self.monofy_value) def monofy(self, value): self.isMonofied = value if value: self.filterMono.play() self.mixedFilterMono.play() self.interp.set("interp", 0, 0.01, self.stopped) else: self.scaledAzimuth.play() self.azimuthMulti.play() self.filterMulti.play() self.interp.set("interp", 1, 0.01, self.stopped) def stopped(self): if self.isMonofied: self.scaledAzimuth.stop() self.azimuthMulti.stop() self.filterMulti.stop() else: self.filterMono.stop() self.mixedFilterMono.stop() Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="azimuth", label="Azimuth", min=-180, max=180, init=0, unit="deg", col="orange"), cslider(name="elevation", label="Elevation", min=0, max=90, init=0, unit="deg", col="purple1"), cslider(name="azispan", label="Azimuth Span", min=0, max=1, init=0, unit="%", col="orange3"), cslider(name="elespan", label="Elevation Span", min=0, max=1, init=0, unit="%", col="purple3"), ctoggle(name="monofy", label="Monofy Source", init=False, col="blue"), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/BrickWall.c5000066400000000000000000000056521372272363700223600ustar00rootroot00000000000000class Module(BaseModule): """ "Convolution brickwall lowpass/highpass/bandpass/bandstop filter" Description Convolution filter with a user-defined length sinc kernel. This kind of filters are very CPU expensive but can give quite good stopband attenuation. Sliders # Cutoff Frequency : Cutoff frequency, in Hz, of the filter. # Bandwidth : Bandwith, in Hz, of the filter. Used only by bandpass and pnadstop filters. # Filter Order : Number of points of the filter kernel. A longer kernel means a sharper attenuation (and a higher CPU cost). This value is only available at initialization time. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of the filter (lowpass, highpass, bandpass, bandstop) # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.deg = IRWinSinc(self.snd, freq=self.freq, bw=self.bw, type=self.type_index, order=int(self.order.get()), mul=self.env) #BALANCE self.osc = Sine(10000, mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd def type(self, index, value): self.deg.type = index def order_up(self, value): pass Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="freq", label="Cutoff Frequency", min=20, max=18000, init=1000, rel="log", unit="Hz", col="green1"), cslider(name="bw", label="Bandwidth", min=20, max=18000, init=1000, rel="log", unit="Hz", col="green2"), cslider(name="order", label="Filter Order", min=32, max=1024, init=256, res="int", rel="lin", up=True, col="grey"), cpopup(name="type", label="Filter Type", value=["Lowpass", "Highpass","Bandstop","Bandpass"], init="Lowpass", col="green1"), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/MaskFilter.c5000066400000000000000000000072551372272363700225500ustar00rootroot00000000000000class Module(BaseModule): """ "Ranged filter module using lowpass and highpass filters" Description The signal is first lowpassed and then highpassed to create a bandpass filter with independant lower and higher boundaries. The user can interpolate between two such filters. Sliders # Filter 1 Limits : Range of the first filter (min = highpass, max = lowpass) # Filter 2 Limits : Range of the second filter (min = highpass, max = lowpass) # Mix : Balance between filter 1 and filter 2 Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Number of Stages : Amount of stacked biquad filters # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.lp = Biquadx(input=self.snd, freq=self.fultrange[1], q=1, type=0, stages=int(self.filtnum_value), mul=1) self.hp = Biquadx(input=self.lp, freq=self.fultrange[0], q=1, type=1, stages=int(self.filtnum_value), mul=0.5*self.env) self.lp2 = Biquadx(input=self.snd, freq=self.filtrangeCAC[1], q=1, type=0, stages=int(self.filtnum_value), mul=1) self.hp2 = Biquadx(input=self.lp2, freq=self.filtrangeCAC[0], q=1, type=1, stages=int(self.filtnum_value), mul=0.5*self.env) #BALANCE self.osc = Sine(10000,mul=.1) self.balanced1 = Balance(self.hp, self.osc, freq=10) self.balanced2 = Balance(self.hp2, self.osc, freq=10) self.out1 = Interp(self.hp, self.balanced1) self.out2 = Interp(self.hp2, self.balanced2) self.out = Interp(self.out1, self.out2, self.mix, mul=0.5) #INIT self.balance(self.balance_index, self.balance_value) def filtnum(self, index, value): self.lp.stages = int(value) self.hp.stages = int(value) self.lp2.stages = int(value) self.hp2.stages = int(value) def balance(self,index,value): if index == 0: self.out1.interp = 0 self.out2.interp = 0 elif index ==1: self.out1.interp = 1 self.out2.interp = 1 self.balanced1.input2 = self.osc self.balanced2.input2 = self.osc elif index == 2: self.out1.interp = 1 self.out2.interp = 1 self.balanced1.input2 = self.snd self.balanced2.input2 = self.snd Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), crange(name="fultrange", label="Filter 1 Limits", min=20, max=20000, init = [100,200], rel="log", unit="Hz", col="green1"), crange(name="filtrangeCAC", label="Filter 2 Limits", min=20, max=20000, init = [1000,2000], rel="log", unit="Hz", col="green2"), cslider(name="mix", label = "Mix", min=0,max=1,init=0.5,rel="lin", unit="%",col="blue1"), cpopup(name="filtnum", label="Number of Stages", init="4", col="orange1", value=["1","2","3","4","5","6"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/ParamEQ.c5000066400000000000000000000126411372272363700217700ustar00rootroot00000000000000class Module(BaseModule): """ "Parametric equalizer" Description Standard parametric equalizer built with four lowshelf/highshelf/peak/notch filters. Sliders # Freq 1 Boost/Cut : Gain of the first EQ # Freq 1 : Center frequency of the first EQ # Freq 1 Q : Q factor of the first EQ # Freq 2 Boost/Cut : Gain of the second EQ # Freq 2 : Center frequency of the second EQ # Freq 2 Q : Q factor of the second EQ # Freq 3 Boost/Cut : Gain of the third EQ # Freq 3 : Center frequency of the third EQ # Freq 3 Q : Q factor of the third EQ # Freq 5 Boost/Cut : Gain of the fourth EQ # Freq 4 : Center frequency of the fourth EQ # Freq 5 Q : Q factor of the fourth EQ Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # EQ Type 1 : EQ type of the first EQ # EQ Type 2 : EQ type of the second EQ # EQ Type 3 : EQ type of the third EQ # EQ Type 4 : EQ type of the fourth EQ # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.eq1 = EQ(self.snd, freq=self.freq1, q=self.freq1q, boost=self.freq1gain, type=self.eq1type_index) self.eq2 = EQ(self.eq1, freq=self.freq2, q=self.freq2q, boost=self.freq2gain, type=self.eq2type_index) self.eq3 = EQ(self.eq2, freq=self.freq3, q=self.freq3q, boost=self.freq3gain, type=self.eq3type_index) self.eq4 = EQ(self.eq3, freq=self.freq4, q=self.freq4q, boost=self.freq4gain, type=self.eq4type_index, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.eq4, self.osc, freq=10) self.out = Interp(self.eq4, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def eq1type(self, index, value): self.eq1.type = index def eq2type(self, index, value): self.eq2.type = index def eq3type(self, index, value): self.eq3.type = index def eq4type(self, index, value): self.eq4.type = index def balance(self,index,value): if index == 0: self.out.interp = 0 elif index == 1: self.out.interp= 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="freq1gain", label="Freq 1 Boost/Cut", min=-48, max=24, init=-3, rel="lin", unit="dB", col="purple1"), cslider(name="freq1", label="Freq 1", min=1, max=20000, init=500, rel="log", unit="Hz", col="purple1", half=True), cslider(name="freq1q", label="Freq 1 Q", min=0.5, max=10, init=0.707, rel="lin", unit="x", col="purple1",half=True), cslider(name="freq2gain", label="Freq 2 Boost/Cut", min=-48, max=24, init=-3, rel="lin", unit="dB", col="red"), cslider(name="freq2", label="Freq 2", min=1, max=20000, init=1000, rel="log", unit="Hz", col="red", half=True), cslider(name="freq2q", label="Freq 2 Q", min=0.5, max=10, init=0.707, rel="lin", unit="x", col="red", half=True), cslider(name="freq3gain", label="Freq 3 Boost/Cut", min=-48, max=24, init=-3, rel="lin", unit="dB", col="blue1"), cslider(name="freq3", label="Freq 3", min=1, max=20000, init=1500, rel="log", unit="Hz", col="blue1", half=True), cslider(name="freq3q", label="Freq 3 Q", min=0.5, max=10, init=0.707, rel="lin", unit="x", col="blue1", half=True), cslider(name="freq4gain", label="Freq 4 Boost/Cut", min=-48, max=24, init=-3, rel="lin", unit="dB", col="green1"), cslider(name="freq4", label="Freq 4", min=1, max=20000, init=2000, rel="log", unit="Hz", col="green1", half=True), cslider(name="freq4q", label="Freq 4 Q", min=0.5, max=10, init=0.707, rel="lin", unit="x", col="green1", half=True), cpopup(name="eq1type", label="EQ 1 Type", init="Lowshelf", col="purple1", value=["Peak/Notch", "Lowshelf", "Highshelf"]), cpopup(name="eq2type", label="EQ 2 Type", init="Peak/Notch", col="red", value=["Peak/Notch", "Lowshelf", "Highshelf"]), cpopup(name="eq3type", label="EQ 3 Type", init="Peak/Notch", col="blue1", value=["Peak/Notch", "Lowshelf", "Highshelf"]), cpopup(name="eq4type", label="EQ 4 Type", init="Highshelf", col="green1", value=["Peak/Notch", "Lowshelf", "Highshelf"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/Phaser.c5000066400000000000000000000047451372272363700217320ustar00rootroot00000000000000class Module(BaseModule): """ "Multi-stages second-order phase shifter allpass filters" Description Phaser implements a multi-stages second-order allpass filters, which, when mixed with the original signal, create a serie of peaks/notches in the sound. Sliders # Base Freq : Center frequency of the first notch of the phaser # Q Factor : Q factor (resonance) of the phaser notches # Notch Spread : Distance between phaser notches # Feedback : Amount of phased signal fed back into the phaser # Dry / Wet : Mix between the original signal and the phased signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Number of Stages : Changes notches bandwidth (stacked filters), only available at initialization time # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.phaser = Phaser(input=self.snd, freq=self.centerfreq, spread=self.spread, q=self.fq, feedback=self.fb, num=self.stages_index+1, mul=0.5) self.out = Interp(self.snd, self.phaser, self.drywet, mul=self.env) Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="centerfreq", label="Base Freq", min=20, max=15000, init=100, rel="log", unit="Hz", col="green1"), cslider(name="fq", label="Q Factor", min=0.5, max=10, init=5, rel="lin", unit="Q", col="green2"), cslider(name="spread", label="Notch Spread", min=0.1, max=2, init=1.25, rel="lin", unit="x", col="green3"), cslider(name="fb", label="Feedback", min=0, max=0.999, init=0.7, rel="lin", unit="x", col="purple1"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=0.8, rel="lin", unit="x", col="blue1"), cpopup(name="stages", label="Number of Stages", init="16", rate="i", col="grey", value=[str(i+1) for i in range(64)]), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/StateVar.c5000066400000000000000000000051101372272363700222240ustar00rootroot00000000000000class Module(BaseModule): """ "State Variable Filter" Description This module implements lowpass, bandpass and highpass filters in parallel and allow the user to interpolate on an axis lp -> bp -> hp. Sliders # Filter Freq : Cutoff frequency for lp and hp (center freq for bp) # Filter Q : Q factor (inverse of bandwidth) of the filter # Type (lp->bp->hp) : Interpolating factor between filters # Dry / Wet : Mix between the original and the filtered signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Chords : Pitch interval between voices (chords), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.dsp = SVF(self.snd, self.freq, self.q, self.type) self.deg = Interp(self.snd, self.dsp, self.drywet, mul=self.env) #BALANCE self.osc = Sine(10000, mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="freq", label="Filter Freq", min=20, max=20000, init=1000, rel="log", unit="Hz", col="green1"), cslider(name="q", label="Filter Q", min=0.5, max=25, init=1, rel="log", unit="x", col="green2"), cslider(name="type", label="Type (lp->bp->hp)", min=0, max=1, init=0.5, rel="lin", unit="x", col="green3"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/StateVar2.c5000066400000000000000000000075021372272363700223150ustar00rootroot00000000000000class Module(BaseModule): """ "State Variable Filter, version 2 !" Description This module implements a second-order state variable filter (SVF) which allow to interpolate over various filter types, organized in a user-defined order (see `Filter Order` parameter below). This 2-pole multimode filter is described in the book "The Art of VA Filter Design" by Vadim Zavalishin (version 2.1.0). Sliders # Filter Freq : Cutoff or center frequency of the filter. # Filter Q : Q factor (inverse of bandwidth) of the filter. # Filter Shelf : Gain, in dB, used by shelving filters. # Type : Interpolating factor between filters # Dry / Wet : Mix between the original and the filtered signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Filter Order : The ordering is a list of one to ten integers indicating the filter types order used by the `Type` parameter to crossfade between them. Types, as integer, are: 0 = lowpass 1 = bandpass 2 = highpass 3 = highshelf 4 = bandshelf 5 = lowshelf 6 = notch 7 = peak 8 = allpass # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Chords : Pitch interval between voices (chords), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.filtOrder = [0, 1, 2, 3, 4, 5, 6, 7, 8] self.filterType = Sig(self.type, mul=len(self.filtOrder)) self.dsp = SVF2(self.snd, freq=self.freq, q=self.q, shelf=self.shelf, type=self.filterType) self.dsp.order = self.filtOrder self.deg = Interp(self.snd, self.dsp, self.drywet, mul=self.env) #BALANCE self.osc = Sine(10000, mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd def order(self, value): self.filtOrder = value[:] self.dsp.order = self.filtOrder self.filterType.mul = len(self.filtOrder) Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="freq", label="Filter Freq", min=20, max=20000, init=1000, rel="log", unit="Hz", col="green1"), cslider(name="q", label="Filter Q", min=0.5, max=25, init=1, rel="log", unit="x", col="green2"), cslider(name="shelf", label="Filter Shelf", min=-12, max=12, init=-6, rel="lin", unit="dB", col="green3"), cslider(name="type", label="Type", min=0, max=1, init=0.5, rel="lin", unit="x", col="green4"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cgen(name="order", label="Filter Order", init=[0,1,2,3,4,5,6,7,8], col="green1"), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Filters/Vocoder.c5000066400000000000000000000101061372272363700220750ustar00rootroot00000000000000class Module(BaseModule): """ "Time domain vocoder effect" Description Applies the spectral envelope of a first sound to the spectrum of a second sound. The vocoder is an analysis/synthesis system, historically used to reproduce human speech. In the encoder, the first input (spectral envelope) is passed through a multiband filter, each band is passed through an envelope follower, and the control signals from the envelope followers are communicated to the decoder. The decoder applies these (amplitude) control signals to corresponding filters modifying the second source (exciter). Sliders # Base Frequency : Center frequency of the first band. This is the base frequency used tocompute the upper bands. # Frequency Spread : Spreading factor for upper band frequencies. Each band is `freq * pow(order, spread)`, where order is the harmonic rank of the band. # Q Factor : Q of the filters as `center frequency / bandwidth`. Higher values imply more resonance around the center frequency. # Time Response : Time response of the envelope follower. Lower values mean smoother changes, while higher values mean a better time accuracy. # Gain : Output gain of the process in dB. # Num of Bands : The number of bands in the filter bank. Defines the number of notches in the spectrum. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.spec = self.addSampler("spec") self.exci = self.addSampler("exci") self.proc = Vocoder(self.spec, self.exci, freq=self.freq, spread=self.spread, q=self.q, slope=self.slope, stages=int(self.stages.get()), mul=DBToA(self.gain)) self.deg = Mix(self.proc, voices=self.nchnls, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self,index,value): if index == 0: self.out.interp= 0 elif index ==1: self.out.interp= 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.spec else: self.out.interp = 1 self.balanced.input2 = self.exci def stages_up(self, value): self.proc.stages = int(value) Interface = [ csampler(name="spec", label="Spectral Envelope"), csampler(name="exci", label="Exciter"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="freq", label="Base Frequency", min=10, max=1000, init=80, rel="log", unit="Hz", col="green1"), cslider(name="spread", label="Frequency Spread", min=0.25, max=2, init=1.25, rel="log", unit="x", col="green2"), cslider(name="q", label="Q Factor", min=0.5, max=200, init=20, rel="log", unit="Q", col="green3"), cslider(name="slope", label="Time Response", min=0, max=1, init=0.5, rel="lin", unit="x", col="orange1"), cslider(name="gain", label="Gain", min=-90, max=18, init=0, rel="lin", unit="dB", col="purple1"), cslider(name="stages", label="Num of bands", min=4, max=64, init=20, rel="lin", res="int", unit="x", up=True), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Spectral", "Exciter"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Multiband/000077500000000000000000000000001372272363700205545ustar00rootroot00000000000000cecilia5-5.4.1/Resources/modules/Multiband/MultiBandBeatMaker.c5000066400000000000000000000262741372272363700244530ustar00rootroot00000000000000class Module(BaseModule): """ "Multi-band algorithmic beatmaker" Description MultiBandBeatMaker uses four algorithmic beat generators to play spectral separated chunks of a sound. Sliders # Frequency Splitter : Split points for multi-band processing # Num of Taps : Number of taps in a measure # Tempo : Speed of taps # Tap Length : Length of taps # Beat 1 Index : Soundfile index of the first beat # Beat 2 Index : Soundfile index of the second beat # Beat 3 Index : Soundfile index of the third beat # Beat 4 Index : Soundfile index of the fourth beat # Beat 1 Distribution : Repartition of taps for the first beat (100% weak --> 100% down) # Beat 2 Distribution : Repartition of taps for the second beat (100% weak --> 100% down) # Beat 3 Distribution : Repartition of taps for the third beat (100% weak --> 100% down) # Beat 4 Distribution : Repartition of taps for the fourth beat (100% weak --> 100% down) # Beat 1 Gain : Gain of the first beat # Beat 2 Gain : Gain of the second beat # Beat 3 Gain : Gain of the third beat # Beat 4 Gain : Gain of the fourth beat # Global Seed : Seed value for the algorithmic beats, using the same seed with the same distributions will yield the exact same beats Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Beat 1 ADSR : Envelope of taps for the first beat in breakpoint fashion # Beat 2 ADSR : Envelope of taps for the second beat in breakpoint fashion # Beat 3 ADSR : Envelope of taps for the third beat in breakpoint fashion # Beat 4 ADSR : Envelope of taps for the fourth beat in breakpoint fashion Popups & Toggles # Polyphony per Voice : The number of streams used for each voice's polpyhony. High values allow more overlaps but are more CPU expensive, only available at initialization time. """ def __init__(self): BaseModule.__init__(self) self.snd = self.addFilein("snd") self.last_we1 = self.last_we2 = self.last_we3 = self.last_we4 = self.last_taps = -1 self.rtempo = 1/(self.tempo/15) self.setGlobalSeed(int(self.seed.get())) POLY = self.poly_index + 1 self.beat1 = Beat(time=self.rtempo, taps=int(self.taps.get()), w1=80, w2=40, w3=20, poly=POLY).play() self.beat2 = Beat(time=self.rtempo, taps=int(self.taps.get()), w1=50, w2=100, w3=50, poly=POLY).play() self.beat3 = Beat(time=self.rtempo, taps=int(self.taps.get()), w1=30, w2=60, w3=70, poly=POLY).play() self.beat4 = Beat(time=self.rtempo, taps=int(self.taps.get()), w1=15, w2=30, w3=90, poly=POLY).play() self.tre1 = TrigEnv(input=self.beat1, table=self.adsr1, dur=self.tapsl, mul=self.beat1['amp']) self.tre2 = TrigEnv(input=self.beat2, table=self.adsr2, dur=self.tapsl, mul=self.beat2['amp']) self.tre3 = TrigEnv(input=self.beat3, table=self.adsr3, dur=self.tapsl, mul=self.beat3['amp']) self.tre4 = TrigEnv(input=self.beat4, table=self.adsr4, dur=self.tapsl, mul=self.beat4['amp']) self.linseg1 = TrigLinseg(input=self.beat1, list=[(0,self.bindex1.get()),(self.tapsl.get(),1/(self.snd.getDur(False)/self.tapsl.get())+self.bindex1.get())]) self.linseg2 = TrigLinseg(input=self.beat2, list=[(0,self.bindex2.get()),(self.tapsl.get(),(1/(self.snd.getDur(False)/self.tapsl.get()))+self.bindex2.get())]) self.linseg3 = TrigLinseg(input=self.beat3, list=[(0,self.bindex3.get()),(self.tapsl.get(),(1/(self.snd.getDur(False)/self.tapsl.get()))+self.bindex3.get())]) self.linseg4 = TrigLinseg(input=self.beat4, list=[(0,self.bindex4.get()),(self.tapsl.get(),(1/(self.snd.getDur(False)/self.tapsl.get()))+self.bindex4.get())]) self.trf1 = TrigFunc(self.linseg1['trig'], self.newseg1) self.trf2 = TrigFunc(self.linseg2['trig'], self.newseg2) self.trf3 = TrigFunc(self.linseg3['trig'], self.newseg3) self.trf4 = TrigFunc(self.linseg4['trig'], self.newseg4) self.again1 = DBToA(self.gain1) self.again2 = DBToA(self.gain2) self.again3 = DBToA(self.gain3) self.again4 = DBToA(self.gain4) self.pointer1 = ButLP(Pointer(table=self.snd, index=self.linseg1, mul=self.tre1*self.again1), freq=self.splitter[0]) self.pointer2 = ButHP(ButLP(Pointer(table=self.snd, index=self.linseg2, mul=self.tre2*self.again2), freq=self.splitter[1]), freq=self.splitter[0]) self.pointer3 = ButHP(ButLP(Pointer(table=self.snd, index=self.linseg3, mul=self.tre3*self.again3), freq=self.splitter[2]), freq=self.splitter[1]) self.pointer4 = ButHP(Pointer(table=self.snd, index=self.linseg4, mul=self.tre4*self.again4), freq=self.splitter[2]) self.out = Mix(self.pointer1+self.pointer2+self.pointer3+self.pointer4, voices=self.nchnls, mul=self.env) self.trigend = TrigFunc(self.beat1["end"], self.newdist) def seed_up(self, value): self.setGlobalSeed(int(value)) self.beat1.new() self.beat2.new() self.beat3.new() def newseg1(self): self.linseg1.setList([(0,self.bindex1.get()),(self.tapsl.get(),1/(self.snd.getDur(False)/self.tapsl.get())+self.bindex1.get())]) def newseg2(self): self.linseg2.setList([(0,self.bindex2.get()),(self.tapsl.get(),1/(self.snd.getDur(False)/self.tapsl.get())+self.bindex2.get())]) def newseg3(self): self.linseg3.setList([(0,self.bindex3.get()),(self.tapsl.get(),1/(self.snd.getDur(False)/self.tapsl.get())+self.bindex3.get())]) def newseg4(self): self.linseg4.setList([(0,self.bindex4.get()),(self.tapsl.get(),1/(self.snd.getDur(False)/self.tapsl.get())+self.bindex4.get())]) def newtaps(self, value): self.beat1.setTaps(value) self.beat2.setTaps(value) self.beat3.setTaps(value) self.beat4.setTaps(value) def newdist1(self, value): w1 = int(value) if value <= 50: w2 = int(value*2) else: w2 = int((100-value)*2) w3 = int(100-value) self.beat1.setW1(w1) self.beat1.setW2(w2) self.beat1.setW3(w3) def newdist2(self, value): w1 = int(value) if value <= 50: w2 = int(value*2) else: w2 = int((100-value)*2) w3 = int(100-value) self.beat2.setW1(w1) self.beat2.setW2(w2) self.beat2.setW3(w3) def newdist3(self, value): w1 = int(value) if value <= 50: w2 = int(value*2) else: w2 = int((100-value)*2) w3 = int(100-value) self.beat3.setW1(w1) self.beat3.setW2(w2) self.beat3.setW3(w3) def newdist4(self, value): w1 = int(value) if value <= 50: w2 = int(value*2) else: w2 = int((100-value)*2) w3 = int(100-value) self.beat4.setW1(w1) self.beat4.setW2(w2) self.beat4.setW3(w3) def newdist(self): taps = int(self.taps.get()) if taps != self.last_taps: self.last_taps = taps self.newtaps(taps) value = int(self.we1.get()) if value != self.last_we1: self.last_we1 = value self.newdist1(value) value = int(self.we2.get()) if value != self.last_we2: self.last_we2 = value self.newdist2(value) value = int(self.we3.get()) if value != self.last_we3: self.last_we3 = value self.newdist3(value) value = int(self.we4.get()) if value != self.last_we4: self.last_we4 = value self.newdist4(value) Interface = [ cfilein(name="snd", label="Audio"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), csplitter(name="splitter", label="Frequency Splitter", min=100, max=18000, init=[150, 500, 2000], num_knobs=3, rel="log", unit="Hz", col="grey"), cgraph(name="adsr1", label="Beat 1 ADSR", func=[(0,0),(0.04,1),(0.13,0.5),(0.8,0.5),(1,0)], table=True, col="purple1"), cgraph(name="adsr2", label="Beat 2 ADSR", func=[(0,0),(0.03,1),(0.12,0.5),(0.7,0.45),(1,0)], table=True, col="red1"), cgraph(name="adsr3", label="Beat 3 ADSR", func=[(0,0),(0.02,1),(0.11,0.5),(0.6,0.4),(1,0)], table=True, col="green1"), cgraph(name="adsr4", label="Beat 4 ADSR", func=[(0,0),(0.01,1),(0.1,0.5),(0.5,0.35),(1,0)], table=True, col="blue1"), cslider(name="taps", label="Num of Taps", min=1, max=64, init=16, res="int", rel="lin", unit="x", col="orange1"), cslider(name="tempo", label="Tempo", min=20, max=240, gliss=0, init=120, rel="lin", unit="bpm", col="orange2"), cslider(name="tapsl", label="Tap Length", min=0.1, max=1, init=0.1, rel="lin", unit="sec", col="orange3"), cslider(name="bindex1", label="Beat 1 Index", min=0, max=1, init=0, rel="lin", unit="x", col="purple1",half=True), cslider(name="bindex2", label="Beat 2 Index", min=0, max=1, init=0.4, rel="lin", unit="x", col="red1",half=True), cslider(name="we1", label="Beat 1 Distribution", min=0, max=100, init=80, res="int", rel="lin", unit="%", col="purple2",half=True), cslider(name="we2", label="Beat 2 Distribution", min=0, max=100, init=80, res="int", rel="lin", unit="%", col="red2",half=True), cslider(name="gain1", label="Beat 1 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="purple3",half=True), cslider(name="gain2", label="Beat 2 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="red3",half=True), cslider(name="bindex3", label="Beat 3 Index", min=0, max=1, init=0.8, rel="lin", unit="x", col="green1",half=True), cslider(name="bindex4", label="Beat 4 Index", min=0, max=1, init=0.9, rel="lin", unit="x", col="blue1",half=True), cslider(name="we3", label="Beat 3 Distribution", min=0, max=100, init=30, rel="lin", res="int", unit="%", col="green2",half=True), cslider(name="we4", label="Beat 4 Distribution", min=0, max=100, init=20, rel="lin", res="int", unit="%", col="blue2",half=True), cslider(name="gain3", label="Beat 3 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="green3",half=True), cslider(name="gain4", label="Beat 4 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue3",half=True), cslider(name="seed", label="Global seed", min=0, max=5000, init=0, rel="lin", res="int", unit="x", up=True), cpopup(name="poly", label = "Polyphony per Voice", init= "4", rate="i", col="grey", value=[str(i+1) for i in range(8)]), ] cecilia5-5.4.1/Resources/modules/Multiband/MultiBandDelay.c5000066400000000000000000000120471372272363700236470ustar00rootroot00000000000000class Module(BaseModule): """ "Multi-band delay with feedback" Description MultiBandDelay implements four separated spectral band delays with independent delay time, gain and feedback. Sliders # Frequency Splitter : Split points for multi-band processing # Delay Band 1 : Delay time for the first band # Feedback Band 1 : Amount of delayed signal fed back into the first band delay # Gain Band 1 : Gain of the delayed first band # Delay Band 2 : Delay time for the second band # Feedback Band 2 : Amount of delayed signal fed back into the second band delay # Gain Band 2 : Gain of the delayed second band # Delay Band 3 : Delay time for the third band # Feedback Band 3 : Amount of delayed signal fed back into the third band delay # Gain Band 3 : Gain of the delayed third band # Delay Band 4 : Delay time for the fourth band # Feedback Band 4 : Amount of delayed signal fed back into the fourth band delay # Gain Band 4 : Gain of the delayed fourth band # Dry / Wet : Mix between the original signal and the delayed signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") freqs = self.splitter.get(True) self.FBfade = SigTo(value=1, time=.01, init=1) self.split = FourBand(input=self.snd, freq1=freqs[0], freq2=freqs[1], freq3=freqs[2], mul=self.FBfade) self.dels = self.duplicate([self.del1,self.del2,self.del3,self.del4], len(self.snd)) self.fbs = self.duplicate([self.fb1,self.fb2,self.fb3,self.fb4], len(self.snd)) self.mul1 = DBToA(self.gain1) self.mul2 = DBToA(self.gain2) self.mul3 = DBToA(self.gain3) self.mul4 = DBToA(self.gain4) self.muls = self.duplicate([self.mul1,self.mul2,self.mul3,self.mul4], len(self.snd)) self.delay = Delay(input=self.split, delay=self.dels, feedback=self.fbs, maxdelay=15, mul=self.muls).mix(self.nchnls) self.out = Interp(self.snd, self.delay, self.drywet, mul=0.5*self.env) def splitter_up(self, value): self.FBfade.value = 0 time.sleep(.02) self.split.freq1 = value[0] self.split.freq2 = value[1] self.split.freq3 = value[2] time.sleep(.02) self.FBfade.value = 1 Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), csplitter(name="splitter", label="Frequency Splitter", min=100, max=18000, init=[150, 500, 2000], num_knobs=3, rel="log", gliss=0, up=True, unit="Hz", col="grey"), cslider(name="del1", label="Delay Band 1", min=0.0001, max=15, init=0.5, gliss=0.1, rel="log", unit="sec", half=True, col="purple1"), cslider(name="del2", label="Delay Band 2", min=0.0001, max=15, init=0.25, gliss=0.1, rel="log", unit="sec", half=True, col="red1"), cslider(name="fb1", label="Feedback Band 1", min=0, max=0.999, init=0.6, rel="lin", unit="x", half=True, col="purple2"), cslider(name="fb2", label="Feedback Band 2", min=0, max=0.999, init=0.6, rel="lin", unit="x", half=True, col="red2"), cslider(name="gain1", label="Gain Band 1", min=-48, max=18, init=0, rel="lin", unit="dB", half=True, col="purple3"), cslider(name="gain2", label="Gain Band 2", min=-48, max=18, init=0, rel="lin", unit="dB", half=True, col="red3"), cslider(name="del3", label="Delay Band 3", min=0.0001, max=15, init=0.33, gliss=0.1, rel="log", unit="sec", half=True, col="green1"), cslider(name="del4", label="Delay Band 4", min=0.0001, max=15, init=0.66, gliss=0.1, rel="log", unit="sec", half=True, col="blue1"), cslider(name="fb3", label="Feedback Band 3", min=0, max=0.999, init=0.6, rel="lin", unit="x", half=True, col="green2"), cslider(name="fb4", label="Feedback Band 4", min=0, max=0.999, init=0.6, rel="lin", unit="x", half=True, col="blue2"), cslider(name="gain3", label="Gain Band 3", min=-48, max=18, init=0, rel="lin", unit="dB", half=True, col="green3"), cslider(name="gain4", label="Gain Band 4", min=-48, max=18, init=0, rel="lin", unit="dB", half=True, col="blue3"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpoly() ] cecilia5-5.4.1/Resources/modules/Multiband/MultiBandDisto.c5000066400000000000000000000120141372272363700236650ustar00rootroot00000000000000class Module(BaseModule): """ "Multi-band distortion module" Description MultiBandDisto implements four separated spectral band distortions with independent drive, slope and gain. Sliders # Frequency Splitter : Split points for multi-band processing # Band 1 Drive : Amount of distortion applied on the first band # Band 1 Slope : Harshness of distorted first band # Band 1 Gain : Gain of the distorted first band # Band 2 Drive : Amount of distortion applied on the second band # Band 2 Slope : Harshness of distorted second band # Band 2 Gain : Gain of the distorted second band # Band 3 Drive : Amount of distortion applied on the third band # Band 3 Slope : Harshness of distorted third band # Band 3 Gain : Gain of the distorted third band # Band 4 Drive : Amount of distortion applied on the fourth band # Band 4 Slope : Harshness of distorted fourth band # Band 4 Gain : Gain of the distorted fourth band # Dry / Wet : Mix between the original signal and the delayed signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") freqs = self.splitter.get(True) self.FBfade = SigTo(value=1, time=.01, init=1) self.split = FourBand(input=self.snd, freq1=freqs[0], freq2=freqs[1], freq3=freqs[2], mul=self.FBfade) self.drives = self.duplicate([self.drive1,self.drive2,self.drive3,self.drive4], len(self.snd)) self.slopes = self.duplicate([self.drive1slope,self.drive2slope,self.drive3slope,self.drive4slope], len(self.snd)) self.mul1 = DBToA(self.drive1mul) self.mul2 = DBToA(self.drive2mul) self.mul3 = DBToA(self.drive3mul) self.mul4 = DBToA(self.drive4mul) self.muls = self.duplicate([self.mul1,self.mul2,self.mul3,self.mul4], len(self.snd)) self.disto = Disto(input=self.split, drive=self.drives, slope=self.slopes, mul=[i*0.1 for i in self.muls]).mix(self.nchnls) self.out = Interp(self.snd, self.disto, self.drywet, mul=self.env) def splitter_up(self, value): self.FBfade.value = 0 time.sleep(.02) self.split.freq1 = value[0] self.split.freq2 = value[1] self.split.freq3 = value[2] time.sleep(.02) self.FBfade.value = 1 Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), csplitter(name="splitter", label="Frequency Splitter", min=100, max=18000, init=[150, 500, 2000], num_knobs=3, rel="log", gliss=0, up=True, unit="Hz", col="grey"), cslider(name="drive1", label="Band 1 Drive", min=0, max=1, init=0.5, rel="lin", unit="x", col="purple1", half=True), cslider(name="drive2", label="Band 2 Drive", min=0, max=1, init=0.95, rel="lin", unit="x", col="red1", half=True), cslider(name="drive1slope", label="Band 1 Slope", min=0, max=1, init=0.85, rel="lin", unit="x", col="purple2", half=True), cslider(name="drive2slope", label="Band 2 Slope", min=0, max=1, init=0.8, rel="lin", unit="x", col="red2", half=True), cslider(name="drive1mul", label="Band 1 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="purple3", half=True), cslider(name="drive2mul", label="Band 2 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="red3", half=True), cslider(name="drive3", label="Band 3 Drive", min=0, max=1, init=0.75, rel="lin", unit="x", col="green1", half=True), cslider(name="drive4", label="Band 4 Drive", min=0, max=1, init=0.5, rel="lin", unit="x", col="blue1", half=True), cslider(name="drive3slope", label="Band 3 Slope", min=0, max=1, init=0.75, rel="lin", unit="x", col="green2", half=True), cslider(name="drive4slope", label="Band 4 Slope", min=0, max=1, init=0.5, rel="lin", unit="x", col="blue2", half=True), cslider(name="drive3mul", label="Band 3 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="green3", half=True), cslider(name="drive4mul", label="Band 4 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue3", half=True), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpoly() ] cecilia5-5.4.1/Resources/modules/Multiband/MultiBandFreqShift.c5000066400000000000000000000100061372272363700244750ustar00rootroot00000000000000class Module(BaseModule): """ "Multi-band frequency shifter module" Description MultiBandFreqShift implements four separated spectral band frequency shifters with independent amount of shift and gain. Sliders # Frequency Splitter : Split points for multi-band processing # Freq Shift Band 1 : Shift frequency of first band # Gain Band 1 : Gain of the shifted first band # Freq Shift Band 2 : Shift frequency of second band # Gain Band 2 : Gain of the shifted second band # Freq Shift Band 3 : Shift frequency of third band # Gain Band 3 : Gain of the shifted third band # Freq Shift Band 4 : Shift frequency of fourth band # Gain Band 5 : Gain of the shifted fourth band # Dry / Wet : Mix between the original signal and the shifted signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") freqs = self.splitter.get(True) self.FBfade = SigTo(value=1, time=.01, init=1) self.split = FourBand(input=self.snd, freq1=freqs[0], freq2=freqs[1], freq3=freqs[2], mul=self.FBfade) self.shifts = self.duplicate([self.shift1,self.shift2,self.shift3,self.shift4], len(self.snd)) self.mul1 = DBToA(self.gain1) self.mul2 = DBToA(self.gain2) self.mul3 = DBToA(self.gain3) self.mul4 = DBToA(self.gain4) self.muls = self.duplicate([self.mul1,self.mul2,self.mul3,self.mul4], len(self.snd)) self.realups = FreqShift(self.split, self.shifts, self.muls) self.out = Interp(self.snd, self.realups, self.drywet, mul=self.env*0.2) def splitter_up(self, value): self.FBfade.value = 0 time.sleep(.02) self.split.freq1 = value[0] self.split.freq2 = value[1] self.split.freq3 = value[2] time.sleep(.02) self.FBfade.value = 1 Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), csplitter(name="splitter", label="Frequency Splitter", min=100, max=18000, init=[150, 500, 2000], num_knobs=3, rel="log", gliss=0, up=True, unit="Hz", col="grey"), cslider(name="shift1", label="Freq Shift Band 1", min=-2000, max=2000, init=600, rel="lin", unit="Hz", col="purple1", half=True), cslider(name="gain1", label="Gain Band 1", min=-48, max=18, init=0, rel="lin", unit="dB", col="purple2", half=True), cslider(name="shift2", label="Freq Shift Band 2", min=-2000, max=2000, init=500, rel="lin", unit="Hz", col="red1", half=True), cslider(name="gain2", label="Gain Band 2", min=-48, max=18, init=0, rel="lin", unit="dB", col="red2", half=True), cslider(name="shift3", label="Freq Shift Band 3", min=-2000, max=2000, init=400, rel="lin", unit="Hz", col="green1", half=True), cslider(name="gain3", label="Gain Band 3", min=-48, max=18, init=0, rel="lin", unit="dB", col="green2", half=True), cslider(name="shift4", label="Freq Shift Band 4", min=-2000, max=2000, init=300, rel="lin", unit="Hz", col="orange1", half=True), cslider(name="gain4", label="Gain Band 4", min=-48, max=18, init=0, rel="lin", unit="dB", col="orange2", half=True), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpoly() ] cecilia5-5.4.1/Resources/modules/Multiband/MultiBandGate.c5000066400000000000000000000111761372272363700234730ustar00rootroot00000000000000class Module(BaseModule): """ "Multi-band noise gate module" Description MultiBandGate implements four separated spectral band noise gaters with independent threshold and gain. Sliders # Frequency Splitter : Split points for multi-band processing # Threshold Band 1 : dB value at which the gate becomes active on the first band # Gain Band 1 : Gain of the gated first band # Threshold Band 2 : dB value at which the gate becomes active on the second band # Gain Band 2 : Gain of the gated second band # Threshold Band 3 : dB value at which the gate becomes active on the third band # Gain Band 3 : Gain of the gated third band # Threshold Band 4 : dB value at which the gate becomes active on the fourth band # Gain Band 4 : Gain of the gated fourth band # Rise Time : Time taken by the gate to close # Fall Time : Time taken by the gate to open # Dry / Wet : Mix between the original signal and the shifted signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") freqs = self.splitter.get(True) self.FBfade = SigTo(value=1, time=.01, init=1) self.split = FourBand(input=self.snd, freq1=freqs[0], freq2=freqs[1], freq3=freqs[2], mul=self.FBfade) self.threshs = self.duplicate([self.thresh1,self.thresh2,self.thresh3,self.thresh4], len(self.snd)) self.mul1 = DBToA(self.gain1) self.mul2 = DBToA(self.gain2) self.mul3 = DBToA(self.gain3) self.mul4 = DBToA(self.gain4) self.muls = self.duplicate([self.mul1,self.mul2,self.mul3,self.mul4], len(self.snd)) self.gate = Gate(input=self.split, thresh=self.threshs, risetime=self.gaterise, falltime=self.gatefall, lookahead=5.00, outputAmp=False, mul=self.muls).mix(self.nchnls) self.out = Interp(Delay(self.snd, 0.005, maxdelay=0.005), self.gate, self.drywet, mul=self.env) def splitter_up(self, value): self.FBfade.value = 0 time.sleep(.02) self.split.freq1 = value[0] self.split.freq2 = value[1] self.split.freq3 = value[2] time.sleep(.02) self.FBfade.value = 1 Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), csplitter(name="splitter", label="Frequency Splitter", min=100, max=18000, init=[150, 500, 2000], num_knobs=3, rel="log", gliss=0, up=True, unit="Hz", col="grey"), cslider(name="thresh1", label="Threshold Band 1", min=-80, max=-0.1, init=-60, rel="lin", unit="dB", col="purple1", half=True), cslider(name="gain1", label="Gain Band 1", min=-48, max=18, init=0, rel="lin", unit="dB", col="purple2", half=True), cslider(name="thresh2", label="Threshold Band 2", min=-80, max=-0.1, init=-60, rel="lin", unit="dB", col="red1", half=True), cslider(name="gain2", label="Gain Band 2", min=-48, max=18, init=0, rel="lin", unit="dB", col="red2", half=True), cslider(name="thresh3", label="Threshold Band 3", min=-80, max=-0.1, init=-60, rel="lin", unit="dB", col="green1", half=True), cslider(name="gain3", label="Gain Band 3", min=-48, max=18, init=0, rel="lin", unit="dB", col="green2", half=True), cslider(name="thresh4", label="Threshold Band 4", min=-80, max=-0.1, init=-60, rel="lin", unit="dB", col="blue1", half=True), cslider(name="gain4", label="Gain Band 4", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue2", half=True), cslider(name="gaterise", label="Rise Time", min=0.001, max=1, init=0.01, rel="lin", unit="sec", col="orange1"), cslider(name="gatefall", label="Fall Time", min=0.001, max=1, init=0.01, rel="lin", unit="sec", col="orange2"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpoly() ] cecilia5-5.4.1/Resources/modules/Multiband/MultiBandHarmonizer.c5000066400000000000000000000127261372272363700247330ustar00rootroot00000000000000class Module(BaseModule): """ "Multi-band harmonizer module" Description MultiBandHarmonizer implements four separated spectral band harmonizers with independent transposition, feedback and gain. Sliders # Frequency Splitter : Split points for multi-band processing # Transpo Band 1 : Pitch shift for the first band # Feedback Band 1 : Amount of harmonized signal fed back into the first band harmonizer # Gain Band 1 : Gain of the harmonized first band # Transpo Band 2 : Pitch shift for the second band # Feedback Band 2 : Amount of harmonized signal fed back into the second band harmonizer # Gain Band 2 : Gain of the harmonized second band # Transpo Band 3 : Pitch shift for the third band # Feedback Band 3 : Amount of harmonized signal fed back into the third band harmonizer # Gain Band 3 : Gain of the harmonized third band # Transpo Band 4 : Pitch shift for the fourth band # Feedback Band 4 : Amount of harmonized signal fed back into the fourth band harmonizer # Gain Band 4 : Gain of the harmonized fourth band # Dry / Wet : Mix between the original signal and the harmonized signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Win Size : Harmonizer window size (delay) in seconds # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") freqs = self.splitter.get(True) self.FBfade = SigTo(value=1, time=.01, init=1) self.split = FourBand(input=self.snd, freq1=freqs[0], freq2=freqs[1], freq3=freqs[2], mul=self.FBfade) self.transps = self.duplicate([self.transp1,self.transp2,self.transp3,self.transp4], len(self.snd)) self.mul1 = DBToA(self.gain1) self.mul2 = DBToA(self.gain2) self.mul3 = DBToA(self.gain3) self.mul4 = DBToA(self.gain4) self.muls = self.duplicate([self.mul1,self.mul2,self.mul3,self.mul4], len(self.snd)) self.fbs = self.duplicate([self.fb1, self.fb2, self.fb3, self.fb4], len(self.snd)) self.harm = Harmonizer(input=self.split, transpo=self.transps, feedback=self.fbs, winsize=float(self.winsize_value), mul=self.muls) self.harms = self.harm.mix(self.nchnls) self.out = Interp(self.snd, self.harms, self.drywet, mul=self.env*0.5) def winsize(self, index, value): self.harm.winsize = float(value) def splitter_up(self, value): self.FBfade.value = 0 time.sleep(.02) self.split.freq1 = value[0] self.split.freq2 = value[1] self.split.freq3 = value[2] time.sleep(.02) self.FBfade.value = 1 Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), csplitter(name="splitter", label="Frequency Splitter", min=100, max=18000, init=[150, 500, 2000], num_knobs=3, rel="log", gliss=0, up=True, unit="Hz", col="grey"), cslider(name="transp1", label="Transpo Band 1", min=-24, max=24, init=2, rel="lin", unit="semi", col="purple1", half=True), cslider(name="transp2", label="Transpo Band 2", min=-24, max=24, init=4, rel="lin", unit="semi", col="red1", half=True), cslider(name="fb1", label="Feedback Band 1", min=0, max=0.999, init=0.6, rel="lin", unit="x", col="purple2", half=True), cslider(name="fb2", label="Feedback Band 2", min=0, max=0.999, init=0.5, rel="lin", unit="x", col="red2", half=True), cslider(name="gain1", label="Gain Band 1", min=-48, max=18, init=0, rel="lin", unit="dB", col="purple3", half=True), cslider(name="gain2", label="Gain Band 2", min=-48, max=18, init=0, rel="lin", unit="dB", col="red3", half=True), cslider(name="transp3", label="Transpo Band 3", min=-24, max=24, init=-2, rel="lin", unit="semi", col="green1", half=True), cslider(name="transp4", label="Transpo Band 4", min=-24, max=24, init=-4, rel="lin", unit="semi", col="blue1", half=True), cslider(name="fb3", label="Feedback Band 3", min=0, max=0.999, init=0.5, rel="lin", unit="x", col="green2", half=True), cslider(name="fb4", label="Feedback Band 4", min=0, max=0.999, init=0.6, rel="lin", unit="x", col="blue2", half=True), cslider(name="gain3", label="Gain Band 3", min=-48, max=18, init=0, rel="lin", unit="dB", col="green3", half=True), cslider(name="gain4", label="Gain Band 4", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue3", half=True), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="winsize", label="Win Size", init="0.1", col="orange1", value=["0.025","0.05","0.1","0.15","0.2","0.25","0.5","0.75","1"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Multiband/MultiBandReverb.c5000066400000000000000000000123211372272363700240310ustar00rootroot00000000000000class Module(BaseModule): """ "Multi-band reverberation module" Description MultiBandReverb implements four separated spectral band harmonizers with independent reverb time, cutoff and gain. Sliders # Frequency Splitter : Split points for multi-band processing # Reverb Time 1 : Amount of reverb (tail duration) applied on first band # Lowpass Cutoff 1 : Cutoff frequency of the reverb's lowpass filter (damp) for the first band # Gain 1 : Gain of the reverberized first band # Reverb Time 2 : Amount of reverb (tail duration) applied on second band # Lowpass Cutoff 2 : Cutoff frequency of the reverb's lowpass filter (damp) for the second band # Gain 2 : Gain of the reverberized second band # Reverb Time 3 : Amount of reverb (tail duration) applied on third band # Lowpass Cutoff 3 : Cutoff frequency of the reverb's lowpass filter (damp) for the third band # Gain 3 : Gain of the reverberized third band # Reverb Time 4 : Amount of reverb (tail duration) applied on fourth band # Lowpass Cutoff 4 : Cutoff frequency of the reverb's lowpass filter (damp) for the fourth band # Gain 4 : Gain of the reverberized fourth band # Dry / Wet : Mix between the original signal and the harmonized signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") freqs = self.splitter.get(True) self.FBfade = SigTo(value=1, time=.01, init=1) self.split = FourBand(input=self.snd, freq1=freqs[0], freq2=freqs[1], freq3=freqs[2], mul=self.FBfade) self.fbs = self.duplicate([self.fb1,self.fb2,self.fb3,self.fb4], len(self.snd)) self.cutoffs = self.duplicate([self.cutoff1,self.cutoff2,self.cutoff3,self.cutoff4], len(self.snd)) self.mul1 = DBToA(self.gain1) self.mul2 = DBToA(self.gain2) self.mul3 = DBToA(self.gain3) self.mul4 = DBToA(self.gain4) self.muls = self.duplicate([self.mul1,self.mul2,self.mul3,self.mul4], len(self.snd)) self.verb = WGVerb(input=self.split, feedback=self.fbs, cutoff=self.cutoffs, bal=1, mul=self.muls) self.verbs = self.verb.mix(self.nchnls) self.out = Interp(self.snd, self.verbs, self.drywet, mul=self.env*0.5) def splitter_up(self, value): self.FBfade.value = 0 time.sleep(.02) self.split.freq1 = value[0] self.split.freq2 = value[1] self.split.freq3 = value[2] time.sleep(.02) self.FBfade.value = 1 Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), csplitter(name="splitter", label="Frequency Splitter", min=100, max=18000, init=[150, 500, 2000], num_knobs=3, rel="log", gliss=0, up=True, unit="Hz", col="grey"), cslider(name="fb1", label="Reverb Time 1", min=0, max=0.999, init=0.5, rel="lin", unit="x", col="purple1", half=True), cslider(name="fb2", label="Reverb Time 2", min=0, max=0.999, init=0.75, rel="lin", unit="x", col="red1", half=True), cslider(name="cutoff1", label="Lowpass Cutoff 1", min=20, max=20000, init=2500, rel="log", unit="Hz", col="purple2", half=True), cslider(name="cutoff2", label="Lowpass Cutoff 2", min=20, max=20000, init=4000, rel="log", unit="Hz", col="red2", half=True), cslider(name="gain1", label="Gain 1", min=-48, max=18, init=0, rel="lin", unit="dB", col="purple3", half=True), cslider(name="gain2", label="Gain 2", min=-48, max=18, init=0, rel="lin", unit="dB", col="red3", half=True), cslider(name="fb3", label="Reverb Time 3", min=0, max=0.999, init=0.85, rel="lin", unit="x", col="green1", half=True), cslider(name="fb4", label="Reverb Time 4", min=0, max=0.999, init=0.65, rel="lin", unit="x", col="blue1", half=True), cslider(name="cutoff3", label="Lowpass Cutoff 3", min=20, max=20000, init=5000, rel="log", unit="Hz", col="green2", half=True), cslider(name="cutoff4", label="Lowpass Cutoff 4", min=20, max=20000, init=6000, rel="log", unit="Hz", col="blue2", half=True), cslider(name="gain3", label="Gain 3", min=-48, max=18, init=0, rel="lin", unit="dB", col="green3", half=True), cslider(name="gain4", label="Gain 4", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue3", half=True), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=0.5, rel="lin", unit="x", col="blue1"), cpoly() ] cecilia5-5.4.1/Resources/modules/Pitch/000077500000000000000000000000001372272363700177045ustar00rootroot00000000000000cecilia5-5.4.1/Resources/modules/Pitch/ChordMaker.c5000066400000000000000000000153621372272363700221630ustar00rootroot00000000000000class Module(BaseModule): """ "Sampler-based harmonizer module with multiple voices" Description The input sound is mixed with five real-time, non-stretching, harmonization voices. Sliders # Transpo Voice 1 : Pitch shift of the first voice # Gain Voice 1 : Gain of the transposed first voice # Transpo Voice 2 : Pitch shift of the second voice # Gain Voice 2 : Gain of the transposed second voice # Transpo Voice 3 : Pitch shift of the third voice # Gain Voice 3 : Gain of the transposed third voice # Transpo Voice 4 : Pitch shift of the fourth voice # Gain Voice 4 : Gain of the transposed fourth voice # Transpo Voice 5 : Pitch shift of the fifth voice # Gain Voice 5 : Gain of the transposed fifth voice # Feedback : Amount of transposed signal fed back into the harmonizers (feedback is voice independent) # Dry / Wet : Mix between the original signal and the harmonized signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Win Size : Harmonizer window size in seconds # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Voice Activation (1 --> 5) Mute or unmute each voice independently # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.mul1 = DBToA(self.gain1, mul=self.onoffv1_value) self.mul2 = DBToA(self.gain2, mul=self.onoffv2_value) self.mul3 = DBToA(self.gain3, mul=self.onoffv3_value) self.mul4 = DBToA(self.gain4, mul=self.onoffv4_value) self.mul5 = DBToA(self.gain5, mul=self.onoffv5_value) self.harm1 = Harmonizer(input=self.snd, transpo=self.transp1, feedback=self.fb, winsize=float(self.winsize_value), mul=self.mul1*0.3) self.harm2 = Harmonizer(input=self.snd, transpo=self.transp2, feedback=self.fb, winsize=float(self.winsize_value), mul=self.mul2*0.3) self.harm3 = Harmonizer(input=self.snd, transpo=self.transp3, feedback=self.fb, winsize=float(self.winsize_value), mul=self.mul3*0.3) self.harm4 = Harmonizer(input=self.snd, transpo=self.transp4, feedback=self.fb, winsize=float(self.winsize_value), mul=self.mul4*0.3) self.harm5 = Harmonizer(input=self.snd, transpo=self.transp5, feedback=self.fb, winsize=float(self.winsize_value), mul=self.mul5*0.3) self.harms = self.harm1+self.harm2+self.harm3+self.harm4+self.harm5 self.drydel = Delay(self.snd, delay=float(self.winsize_value)*0.5) self.deg = Interp(self.drydel, self.harms, self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd def winsize(self, index, value): self.harm1.winsize = float(value) self.harm2.winsize = float(value) self.harm3.winsize = float(value) self.harm4.winsize = float(value) self.harm5.winsize = float(value) self.drydel.delay = float(value)*0.5 def onoffv1(self, value): self.mul1.mul = value def onoffv2(self, value): self.mul2.mul = value def onoffv3(self, value): self.mul3.mul = value def onoffv4(self, value): self.mul4.mul = value def onoffv5(self, value): self.mul5.mul = value Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="transp1", label="Transpo Voice 1", min=-24, max=24, init=0, rel="lin", unit="semi", col="red1",half=True), cslider(name="gain1", label="Gain Voice 1", min=-48, max=18, init=0, rel="lin", unit="dB", col="red2",half=True), cslider(name="transp2", label="Transpo Voice 2", min=-24, max=24, init=3, rel="lin", unit="semi", col="purple1",half=True), cslider(name="gain2", label="Gain Voice 2", min=-48, max=18, init=0, rel="lin", unit="dB", col="purple2",half=True), cslider(name="transp3", label="Transpo Voice 3", min=-24, max=24, init=5, rel="lin", unit="semi", col="orange1",half=True), cslider(name="gain3", label="Gain Voice 3", min=-48, max=18, init=0, rel="lin", unit="dB", col="orange2",half=True), cslider(name="transp4", label="Transpo Voice 4", min=-24, max=24, init=-2, rel="lin", unit="semi", col="blue2",half=True), cslider(name="gain4", label="Gain Voice 4", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue3",half=True), cslider(name="transp5", label="Transpo Voice 5", min=-24, max=24, init=-4, rel="lin", unit="semi", col="green1",half=True), cslider(name="gain5", label="Gain Voice 5", min=-48, max=18, init=0, rel="lin", unit="dB", col="green2",half=True), cslider(name="fb", label="Feedback", min=0, max=0.999, init=0, rel="lin", unit="x", col="orange1"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="winsize", label="Win Size", init="0.1", col="blue4", value=["0.025","0.05","0.1","0.15","0.2","0.25","0.5","0.75","1"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), ctoggle(name="onoffv1", label="Voice Activation ( 1 --> 5 )", init=1, stack=True, col="green1"), ctoggle(name="onoffv2", label="", init=1, stack=True, col="green1"), ctoggle(name="onoffv3", label="", init=1, stack=True, col="green1"), ctoggle(name="onoffv4", label="", init=1, stack=True, col="green1"), ctoggle(name="onoffv5", label="", init=1, stack=True, col="green1"), cpoly() ] cecilia5-5.4.1/Resources/modules/Pitch/FreqShift.c5000066400000000000000000000111101372272363700220220ustar00rootroot00000000000000class Module(BaseModule): """ "Two frequency shifters with optional cross-delay and feedback" Description This module implements two frequency shifters from which the output sound of both one can be fed back in the input of the other. Cross- feedback occurs after a user-defined delay of the output sounds. Sliders # Filter Freq : Cutoff or center frequency of the pre-filtering stage # Filter Q : Q factor of the pre-filtering stage # Frequency Shift 1 : Frequency shift, in Hz, of the first voice # Frequency Shift 2 : Frequency shift, in Hz, of the second voice # Feedback Delay : Delay time before the signal is fed back into the delay lines # Feedback : Amount of signal fed back into the delay lines # Feedback Gain : Amount of delayed signal cross-fed back into the frequency shifters. Signal from delay 1 into shifter 2 and signal from delay 2 into shifter 1. # Dry / Wet : Mix between the original signal and the shifted signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of filter # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.biquad = Biquadx(self.snd, freq=self.filter, q=self.filterq, type=self.filttype_index, stages=2, mul=1) self.feed1 = Sig(0, add=self.biquad) self.feed2 = Sig(0, add=self.biquad) self.up1 = FreqShift(input=self.feed1, shift=self.shift1, mul=0.5) self.up2 = FreqShift(input=self.feed2, shift=self.shift2, mul=0.5) self.feeddelay1 = Delay(self.up1, delay=self.delay, feedback=self.feedback, mul=self.gain) self.feeddelay2 = Delay(self.up2, delay=self.delay, feedback=self.feedback, mul=self.gain) self.feed1.value = self.feeddelay2 self.feed2.value = self.feeddelay1 self.deg = Interp(self.snd, self.up1+self.up2, self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def filttype(self, index, value): self.biquad.type = index def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="filter", label="Filter Freq", min=30, max=20000, init=15000, rel="log", unit="Hz", col="green1",half=True), cslider(name="filterq", label="Filter Q", min=0.5, max=10, init=0.707, rel="log", unit="Q", col="green2",half=True), cslider(name="shift1", label="Frequency Shift 1", min=-2000, max=2000, init=500, rel="lin", unit="Hz", col="red1"), cslider(name="shift2", label="Frequency Shift 2", min=-2000, max=2000, init=100, rel="lin", unit="Hz", col="red2"), cslider(name="delay", label="Feedback Delay", min=0.001, max=1, init=.1, rel="lin", unit="sec", col="orange1"), cslider(name="feedback", label="Feedback", min=0, max=0.999, init=0.5, rel="lin", unit="x", col="orange2",half=True), cslider(name="gain", label="Feedback Gain", min=0, max=1, init=0, rel="lin", unit="x", col="orange3",half=True), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="filttype", label="Filter Type", init="Lowpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cpoly() ]cecilia5-5.4.1/Resources/modules/Pitch/Harmonizer.c5000066400000000000000000000073451372272363700222640ustar00rootroot00000000000000class Module(BaseModule): """ "Two voices harmonization module" Description This module implements a two voices harmonization with independent feedback. Sliders # Transpo Voice 1 : Pitch shift of the first voice # Transpo Voice 2 : Pitch shift of the second voice # Feedback : Amount of transposed signal fed back into the harmonizers (feedback is voice independent) # Harm1 / Harm2 : Mix between the first and the second harmonized signals # Dry / Wet : Mix between the original signal and the harmonized signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Win Size Voice 1 : Window size of the first harmonizer (delay) in second # Win Size Voice 2 : Window size of the second harmonizer (delay) in second # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.ws1 = float(self.winsize_value) self.ws2 = float(self.winsize2_value) self.avg_winsize = (self.ws1 + self.ws2) * 0.5 self.snd_del = Delay(self.snd, self.avg_winsize) self.harm1 = Harmonizer(input=self.snd, transpo=self.transp1, feedback=self.fb, winsize=float(self.winsize_value), mul=.5) self.harm2 = Harmonizer(input=self.snd, transpo=self.transp2, feedback=self.fb, winsize=float(self.winsize2_value), mul=.5) self.harms = self.harm1 + (self.harm2 - self.harm1) * self.mix self.dcb = DCBlock(self.harms) self.out = Interp(self.snd_del, self.dcb, self.drywet, mul=self.env) def winsize(self, index, value): self.harm1.winsize = float(value) self.ws1 = float(value) self.avg_winsize = (self.ws1 + self.ws2) * 0.5 self.snd_del.delay = self.avg_winsize def winsize2(self, index, value): if index == 9: self.harm2.winsize = float(self.winsize_value)+0.01 self.ws2 = float(self.winsize_value)+0.01 else: self.harm2.winsize = float(value) self.ws2 = float(value) self.avg_winsize = (self.ws1 + self.ws2) * 0.5 self.snd_del.delay = self.avg_winsize Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="transp1", label="Transpo Voice 1", min=-36, max=36, init=-7, rel="lin", unit="semi", col="red1"), cslider(name="transp2", label="Transpo Voice 2", min=-36, max=36, init=3, rel="lin", unit="semi", col="red2"), cslider(name="fb", label="Feedback", min=0, max=0.999, init=0, rel="lin", unit="x", col="orange1"), cslider(name="mix", label="Harm1 / Harm2", min=0, max=1, init=.5, rel="lin", unit="x", col="green1"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="winsize", label="Win Size Voice 1", init="0.1", col="red1", value=["0.025","0.05","0.1","0.15","0.2","0.25","0.5","0.75","1"]), cpopup(name="winsize2", label="Win Size Voice 2", init="0.1", col="red2", value=["0.025","0.05","0.1","0.15","0.2","0.25","0.5","0.75","1","Voice 1 + 0.01"]), cpoly() ]cecilia5-5.4.1/Resources/modules/Pitch/LooperBank.c5000066400000000000000000000103331372272363700221710ustar00rootroot00000000000000class Module(BaseModule): """ "Sound looper bank with independant pitch and amplitude random" Description Huge oscillator bank (up to 500 players) looping a soundfile stored in a waveform table. The frequencies and amplitudes can be modulated by two random generators with interpolation (each partial have a different set of randoms). Sliders # Transposition : Transposition of the base frequency of the process, given by the sound length. # Frequency Spread : Coefficient of expansion used to compute partial frequencies. If 0, all partials will be at the base frequency. A value of 1 will generate integer harmonics, a value of 2 will skip even harmonics and non-integer values will generate different series of inharmonic frequencies. # Amplitude Slope : Specifies the multiplier in the series of amplitude coefficients. # Boost / Cut : Controls the overall amplitude. # Freq Rand Speed : Frequency, in cycle per second, of the frequency modulations. # Freq Rand Gain : Maximum frequency deviation (positive and negative) in portion of the partial frequency. A value of 1 means that the frequency can drift from 0 Hz to twice the partial frequency. A value of 0 deactivates the frequency deviations. # Amp Rand Speed : Frequency, in cycle per second, of the amplitude modulations. # Amp Rand Gain : Amount of amplitude deviation. 0 deactivates the amplitude modulations and 1 gives full amplitude modulations. # Voices Per Channel : Number of loopers created for each output channel. Changes will be effective on next start. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Partials Freq Jitter : If active, a small jitter is added to the frequency of each partial. For a large number of oscillators and a very small `Frequency Spread`, the periodicity between partial frequencies can cause very strange artefact. Adding a jitter breaks the periodicity. """ def __init__(self): BaseModule.__init__(self) self.snd = self.addFilein("snd") self.gain = DBToA(self.boost, mul=self.env) self.transpo = CentsToTranspo(self.pitch, mul=self.snd.getRate()) self.out = OscBank(self.snd, self.transpo, self.spread, self.slope, self.frndf, self.frnda, self.arndf, self.arnda, [int(self.num.get())]*self.nchnls, self.fjit_value, self.gain).out() def num_up(self, value): pass def fjit(self, value): self.out.fjit = value Interface = [ cfilein(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="pitch", label="Transposition", min=-4800, max=4800, init=0, rel="lin", unit="cnts", col="green1"), cslider(name="spread", label="Frequency Spread", min=0, max=2, init=0.002, rel="lin", unit="x", col="green2"), cslider(name="slope", label="Amplitude Slope", min=0, max=1, init=0.9, rel="lin", unit="x", col="red1"), cslider(name="boost", label="Boost / Cut", min=-32, max=32, init=0, rel="lin", unit="dB", col="red2"), cslider(name="frndf", label="Freq Rand Speed", min=0.001, max=20, init=1, rel="log", unit="Hz", col="green3", half=True), cslider(name="arndf", label="Amp Rand Speed", min=0.001, max=20, init=1, rel="log", unit="Hz", col="red3", half=True), cslider(name="frnda", label="Freq Rand Gain", min=0, max=1, init=0, rel="lin", unit="x", col="green4", half=True), cslider(name="arnda", label="Amp Rand Gain", min=0, max=1, init=0, rel="lin", unit="x", col="red4", half=True), cslider(name="num", label="Voices Per Channel", min=1, max=500, init=100, rel="lin", res="int", unit="x", up=True), ctoggle(name="fjit", label="Partials Freq Jitter", init=False, col="green1"), ]cecilia5-5.4.1/Resources/modules/Pitch/LooperMod.c5000066400000000000000000000114131372272363700220350ustar00rootroot00000000000000import random class Module(BaseModule): """ "Looper module with optional amplitude and frequency modulation" Description A simple soundfile looper with optional amplitude and frequency modulations of variable shapes. Sliders # AM Range : Minimum and maximum amplitude of the Amplitude Modulation LFO # AM Speed : Frequency of the Amplitude Modulation LFO # FM Range : Minimum and maximum amplitude of the Frequency Modulation LFO # FM Speed : Frequency of the Frequency Modulation LFO # Dry / Wet : Mix between the original signal and the processed signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # AM LFO Type : Shape of the amplitude modulation waveform # AM On/Off : Activate or deactivate the amplitude modulation # FM LFO Type : Shape of the frequency modulation waveform # FM On/Off : Activate or deactivate frequency modulation # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addFilein("snd") if type(self.snd.getRate()) == type([]): rate = self.snd.getRate()[0] else: rate = self.snd.getRate() self.lfoam = LFO(freq=self.amspeed, sharp=1, type=self.ampwave_index, mul=0.37, add=0.6) self.lfoamrange = Randi(min=self.amrange[0], max=self.amrange[1], freq=self.amspeed, mul=self.lfoam) self.lfofm = LFO(freq=self.fmspeed, sharp=1, type=self.freqwave_index, mul=0.05, add=1) self.sig1 = Sig(self.fmrange[0]) self.sig2 = Sig(self.fmrange[1]) self.lfofmrange = Randi(min=1-self.sig1, max=1+self.sig2, freq=self.fmspeed, mul=self.lfofm) self.pitrnds = [x*rate for x in self.polyphony_spread for j in range(self.nchnls)] self.ply = [i*self.lfofmrange for i in self.pitrnds] self.index = Phasor(freq=self.pitrnds) self.pointer = Pointer(self.snd, self.index) self.index2 = Phasor(freq=self.pitrnds) self.pointer2 = Pointer(self.snd, self.index2) self.out = Interp(self.pointer2.mix(self.nchnls), self.pointer.mix(self.nchnls), self.drywet, mul=self.polyphony_scaling*0.5*self.env) #INIT self.onoffam(self.onoffam_value) self.onofffm(self.onofffm_value) def ampwave(self, index, value): if index == 0: self.lfoam.sharp = 0.0 else: self.lfoam.sharp = 1.0 self.lfoam.type = (index - 1) % 8 def freqwave(self, index, value): if index == 0: self.lfofm.sharp = 0.0 else: self.lfofm.sharp = 1.0 self.lfofm.type = (index - 1) % 8 def onoffam(self, value): if value == 0: self.pointer.mul = 1 else: self.pointer.mul = self.lfoamrange def onofffm(self, value): if value == 0: self.index.freq = self.pitrnds else: self.index.freq = self.ply Interface = [ cfilein(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), crange(name="amrange", label="AM Range", min=0.001, max=1, init=[0.3,0.5], rel="lin", unit="x", col="green1"), cslider(name="amspeed", label="AM Speed", min=0.001, max=2000, init=8, rel="log", unit="Hz", col="green2"), crange(name="fmrange", label="FM Range", min=0.001, max=0.2, init=[0.01,0.05], rel="lin", unit="x", col="red1"), cslider(name="fmspeed", label="FM Speed", min=0.001, max=2000, init=200, rel="log", unit="Hz", col="red2"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="ampwave", label="AM LFO Type", init="Sine", col="green1", value=["Sine", "Saw Up", "Saw Down", "Square", "Triangle", "Pulse", "Bipolar Pulse", "SAH"]), ctoggle(name="onoffam", label="AM On/Off", init=1, col="green2"), cpopup(name="freqwave", label="FM LFO Type", init="Sine", col="red1", value=["Sine", "Saw Up", "Saw Down", "Square", "Triangle", "Pulse", "Bipolar Pulse", "SAH"]), ctoggle(name="onofffm", label="FM On/Off", init=1, col="red2"), cpoly() ]cecilia5-5.4.1/Resources/modules/Pitch/PitchLooper.c5000066400000000000000000000124741372272363700223750ustar00rootroot00000000000000class Module(BaseModule): """ "Table-based transposition module with multiple voices" Description This module implements five sound loopers playing in parallel. Loopers are table-based so a change in pitch will produce a change in sound duration. This can be useful to create rhythm effects as well as harmonic effects. Sliders # Transpo Voice 1 : Playback speed of the first voice # Gain Voice 1 : Gain of the transposed first voice # Transpo Voice 2 : Playback speed of the second voice # Gain Voice 2 : Gain of the transposed second voice # Transpo Voice 3 : Playback speed of the third voice # Gain Voice 3 : Gain of the transposed third voice # Transpo Voice 4 : Playback speed of the fourth voice # Gain Voice 4 : Gain of the transposed fourth voice # Transpo Voice 5 : Playback speed of the fifth voice # Gain Voice 5 : Gain of the transposed fifth voice Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Voice Activation 1 --> 5 : Mute or unmute each voice independently # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addFilein("snd") self.factor1 = CentsToTranspo(self.transpo1) self.factor2 = CentsToTranspo(self.transpo2) self.factor3 = CentsToTranspo(self.transpo3) self.factor4 = CentsToTranspo(self.transpo4) self.factor5 = CentsToTranspo(self.transpo5) self.phasor1 = Phasor(self.snd.getRate()*self.factor1) self.phasor2 = Phasor(self.snd.getRate()*self.factor2) self.phasor3 = Phasor(self.snd.getRate()*self.factor3) self.phasor4 = Phasor(self.snd.getRate()*self.factor4) self.phasor5 = Phasor(self.snd.getRate()*self.factor5) self.mul1 = DBToA(self.gain1, mul=self.onoffv1_value) self.mul2 = DBToA(self.gain2, mul=self.onoffv2_value) self.mul3 = DBToA(self.gain3, mul=self.onoffv3_value) self.mul4 = DBToA(self.gain4, mul=self.onoffv4_value) self.mul5 = DBToA(self.gain5, mul=self.onoffv5_value) self.voice1 = Pointer(self.snd, self.phasor1, mul=self.mul1*0.2) self.voice2 = Pointer(self.snd, self.phasor2, mul=self.mul2*0.2) self.voice3 = Pointer(self.snd, self.phasor3, mul=self.mul3*0.2) self.voice4 = Pointer(self.snd, self.phasor4, mul=self.mul4*0.2) self.voice5 = Pointer(self.snd, self.phasor5, mul=self.mul5*0.2) self.mixxx = self.voice1+self.voice2+self.voice3+self.voice4+self.voice5 self.out = self.mixxx*self.env def onoffv1(self, value): self.mul1.mul = value def onoffv2(self, value): self.mul2.mul = value def onoffv3(self, value): self.mul3.mul = value def onoffv4(self, value): self.mul4.mul = value def onoffv5(self, value): self.mul5.mul = value Interface = [ cfilein(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="transpo1", label="Transpo Voice 1", min=-4800, max=4800, init=0, rel="lin", unit="cnts", col="green",half=True), cslider(name="gain1", label="Gain Voice 1", min=-48, max=18, init=0, rel="lin", unit="dB", col="green",half=True), cslider(name="transpo2", label="Transpo Voice 2", min=-4800, max=4800, init=10, rel="lin", unit="cnts", col="blue",half=True), cslider(name="gain2", label="Gain Voice 2", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue",half=True), cslider(name="transpo3", label="Transpo Voice 3", min=-4800, max=4800, init=400, rel="lin", unit="cnts", col="orange",half=True), cslider(name="gain3", label="Gain Voice 3", min=-48, max=18, init=0, rel="lin", unit="dB", col="orange",half=True), cslider(name="transpo4", label="Transpo Voice 4", min=-4800, max=4800, init=-300, rel="lin", unit="cnts", col="blue4",half=True), cslider(name="gain4", label="Gain Voice 4", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue4",half=True), cslider(name="transpo5", label="Transpo Voice 5", min=-4800, max=4800, init=-800, rel="lin", unit="cnts", col="green4",half=True), cslider(name="gain5", label="Gain Voice 5", min=-48, max=18, init=0, rel="lin", unit="dB", col="green4",half=True), ctoggle(name="onoffv1", label="Voice Activation 1 --> 5", init=1, stack=True, col="green"), ctoggle(name="onoffv2", label="", init=1, stack=True, col="green"), ctoggle(name="onoffv3", label="", init=1, stack=True, col="green"), ctoggle(name="onoffv4", label="", init=1, stack=True, col="green"), ctoggle(name="onoffv5", label="", init=1, stack=True, col="green"), ] cecilia5-5.4.1/Resources/modules/Pitch/RandomAccumulator.c5000066400000000000000000000116741372272363700235660ustar00rootroot00000000000000class Module(BaseModule): """ "Variable speed recording accumulator module" Description This module records the sound from the sampler in a table at a varying position and speed. The feedback parameter indicates how much of the previous recording at the current position is kept back in the table. The recorded table is read in loop at the normal speed, with possibility to activate a 180 degrees out-of-phase overlap. Sliders # Pre-Filter Freq : Center frequency of the filter applied before the recording # Pre-Filter Q : Q factor of the filter applied before the recording # Accum Feedback : The amount of previous signal in the table that is kept back, mixed with the new recording. # Record Time Rand : The time it took to the recording head to reach the new position target in the table. The target is chosen randomly between 0 and the end of the table. If the time is longer than the distance to run, the signal will be write slowly, so the playback (at regular speed) will give an upward transposition. When the target is reached, a new target and a new recording duration are chosen. # Buffer Length : The size of the table in seconds. This parameter is updated only at the start of the performance. # Global Seed : Root of stochatic generators. If 0, a new value is chosen randomly each time the performance starts. Otherwise, the same root is used every performance, making the generated sequences the same every time. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Recording Envelope : The envelope applied to each recording segment Popups & Toggles # Pre-Filter Type : Type of filter used before the recording # Overlapped : If "On", a second player, 180 degrees out-of-phase, will read the recorded buffer. The signals of both players are summed and sent to the output # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.setGlobalSeed(int(self.seed.get())) duration = self.buflen.get() self.snd = self.addSampler("snd") self.snd_filt = Biquadx(self.snd, freq=self.prefiltf, q=self.prefiltq, type=self.prefilttype_index, stages=2, mul=0.25) self.table = NewTable(length=duration, chnls=self.nchnls, feedback=self.feed.get()) self.dur = RandDur(min=self.min_max[0], max=self.min_max[1]) self.trig = Change(self.dur) self.amp = TrigEnv(self.trig, table=self.envelope, dur=self.dur) self.po = TrigRand(self.trig, 0, duration*self.sr) self.pos = SigTo(self.po, self.dur) self.tw = TableWrite(self.snd_filt*self.amp, self.pos, self.table, 1) self.tf = TrigFunc(self.trig, self.setFeed) self.olapgain = SigTo(self.overlapped_index, time=0.05) self.play1 = Osc(self.table, freq=1.0/duration) self.play2 = Osc(self.table, freq=1.0/duration, phase=0.5, mul=self.olapgain) self.out = (self.play1 + self.play2) * self.env def setFeed(self): self.table.feedback = self.feed.get() def prefilttype(self, index, value): self.snd_filt.type = index def overlapped(self, index, value): self.olapgain.value = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cgraph(name="envelope", label="Recording Envelope", func=[(0,0),(0.15,1),(0.85,1),(1,0)], table=True, curved=True, col="purple1"), cslider(name="prefiltf", label="Pre-Filter Freq", min=100, max=18000, init=15000, rel="log", unit="Hz", col="green1"), cslider(name="prefiltq", label="Pre-Filter Q", min=.5, max=10, init=0.707, rel="log", col="green2"), cslider(name="feed", label="Accum Feedback", min=0, max=1, init=0.5, rel="lin", col="purple1"), crange(name="min_max", label="Record Time Rand", min=0.5, max=10, init=[1,2], rel="log", unit="sec", col="orange1"), cslider(name="buflen", label="Buffer Length", min=1, max=10, init=4, rel="lin", unit="sec", up=True), cslider(name="seed", label="Global Seed", min=0, max=5000, init=0, rel="lin", res="int", unit="x", up=True), cpopup(name="prefilttype", label="Pre-Filter Type", init="Lowpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="overlapped", label="Overlapped", init="Off", col="purple2", value=["Off","On"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Resonators&Verbs/000077500000000000000000000000001372272363700220445ustar00rootroot00000000000000cecilia5-5.4.1/Resources/modules/Resonators&Verbs/ConvolutionReverb.c5000066400000000000000000000025471372272363700257720ustar00rootroot00000000000000class Module(BaseModule): """ "FFT-based convolution reverb" Description This module implements a convolution based on a uniformly partitioned overlap-save algorithm. It can be used to convolve an input signal with an impulse response soundfile to simulate real acoustic spaces. Sliders # Dry / Wet : Mix between the original signal and the convoluted signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.table = self.addFilein("impulse") self.out = CvlVerb(self.snd, self.table.path, bal=self.drywet, mul=self.env) Interface = [ csampler(name="snd", label="Audio"), cfilein(name="impulse", label="Impulse response"), cgraph(name="env"), cslider(name="drywet", label="Mix dry/wet", min=0, max=1, init=0.5, col="blue"), cpoly() ] cecilia5-5.4.1/Resources/modules/Resonators&Verbs/Convolve.c5000066400000000000000000000101031372272363700240630ustar00rootroot00000000000000class Module(BaseModule): """ "Circular convolution filtering module" Description Circular convolution filter where the filter kernel is extract from a soundfile segments. Circular convolution is very expensive to compute, so the impulse response must be kept very short to run in real time. Sliders # Impulse index 1 : Position of the first impulse response in the soundfile (mouse up) # Impulse index 2 : Position of the second impulse response in the soundfile (mouse up) # Imp 1 <--> Imp 2 : Morphing between the two impulse responses # Dry / Wet : Mix between the original signal and the convoluted signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Size : Buffer size of the convolution # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.dump = self.addFilein("sndtable") self.sizeInSec = sampsToSec(int(self.size_value)) if type(self.dump.path) == type([]): p = self.dump.path[0] else: p = self.dump.path self.snddur = sndinfo(p)[1] start = self.snddur*(self.index.get()*0.01) start2 = self.snddur*(self.index2.get()*0.01) self.sndtable = SndTable(self.dump.path, start=start, stop=start+self.sizeInSec) self.sndtable2 = SndTable(self.dump.path, start=start2, stop=start2+self.sizeInSec) self.mtable = NewTable(self.sizeInSec, self.nchnls) self.morphing= TableMorph(self.morph, self.mtable, [self.sndtable, self.sndtable2]) self.convo = Convolve(self.snd, self.mtable, size=int(self.size_value), mul=0.25) self.deg = Interp(self.snd, self.convo, self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def index_up(self, value): start = self.snddur*(value*0.01) self.sndtable.setSound(self.dump.path, start=start, stop=start+self.sizeInSec) def index2_up(self, value): start2 = self.snddur*(value*0.01) self.sndtable2.setSound(self.dump.path, start=start2, stop=start2+self.sizeInSec) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd Interface = [ csampler(name="snd"), cfilein(name="sndtable", label="Impulse"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="index", label="Impulse index 1", min=0, max=100, init=25, rel="lin", unit="%", up=True), cslider(name="index2", label="Impulse index 2", min=0, max=100, init=50, rel="lin", unit="%", up=True), cslider(name="morph", label="Imp 1 <--> Imp 2", min=0, max=1, init=0.5, rel="lin", unit="x", col="red"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="size", label="Size", init="512", col="grey", rate="i", value=["128","256","512","1024","2048","4096","8192"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue", value=["Off","Compress", "Audio"]), cpoly() ]cecilia5-5.4.1/Resources/modules/Resonators&Verbs/DetunedResonators.c5000066400000000000000000000160371372272363700257540ustar00rootroot00000000000000import random class Module(BaseModule): """ "Eight detuned resonators with jitter control" Description This module implements a detuned resonator (waveguide) bank with random controls. This waveguide model consists of one delay-line with a 3-stages recursive allpass filter which made the resonances of the waveguide out of tune. Sliders # Pitch Offset : Base pitch of all the resonators # Amp Random Amp : Amplitude of the jitter applied on the resonators amplitude # Amp Random Speed : Frequency of the jitter applied on the resonators amplitude # Pitch Random Amp : Amplitude of the jitter applied on the resonators frequency # Pitch Random Speed : Frequency of the jitter applied on the resonators frequency # Detune Factor : Detune spread of the resonators # Pitch Voice 1 : Pitch of the first resonator # Pitch Voice 2 : Pitch of the second resonator # Pitch Voice 3 : Pitch of the third resonator # Pitch Voice 4 : Pitch of the fourth resonator # Pitch Voice 5 : Pitch of the fifth resonator # Pitch Voice 6 : Pitch of the sixth resonator # Pitch Voice 7 : Pitch of the seventh resonator # Pitch Voice 8 : Pitch of the eigth resonator # Feedback : Amount of resonators fed back in the resonators # Dry / Wet : Mix between the original signal and the resonators Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Voice Activation ( 1 --> 8 ) : Mute or unmute each of the eigth voices independently # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") chnls = len(self.snd) num = 8 * chnls self.defamp = Sig([0.005 for i in range(num)]) self.ra = Randi(min=1-self.rndamp, max=1+self.rndamp, freq=self.rndspeed*[random.uniform(.95,1.05) for i in range(num)]) self.rf = Randi(min=1-self.rnddelamp, max=1+self.rnddelamp, freq=self.rnddelspeed*[random.uniform(.95,1.05) for i in range(num)]) self.off_transpo = CentsToTranspo(self.offset) self.frs = self.duplicate([self.voice1, self.voice2, self.voice3, self.voice4, self.voice5, self.voice6, self.voice7, self.voice8], chnls) self.freqs = MToF(self.frs, mul=self.off_transpo) self.all_freqs = self.freqs*self.rf self.wgs = AllpassWG(input=self.snd*0.05, freq=self.all_freqs, feed=self.fb, detune=self.detune, minfreq=1, mul=self.ra*self.defamp) self.out = Interp(self.snd, self.wgs.mix(chnls), self.drywet, mul=self.env) #INIT self.onoffv1(self.onoffv1_value) self.onoffv2(self.onoffv2_value) self.onoffv3(self.onoffv3_value) self.onoffv4(self.onoffv4_value) self.onoffv5(self.onoffv5_value) self.onoffv6(self.onoffv6_value) self.onoffv7(self.onoffv7_value) self.onoffv8(self.onoffv8_value) def onoff(self, i, value): l = self.defamp.value l[i*2:i*2+2] = [value, value] self.defamp.value = l def onoffv1(self, value): self.onoff(0, value) def onoffv2(self, value): self.onoff(1, value) def onoffv3(self, value): self.onoff(2, value) def onoffv4(self, value): self.onoff(3, value) def onoffv5(self, value): self.onoff(4, value) def onoffv6(self, value): self.onoff(5, value) def onoffv7(self, value): self.onoff(6, value) def onoffv8(self, value): self.onoff(7, value) Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="offset", label="Pitch Offset", min=-2400, max=2400, init=0, rel="lin", unit="cnts", col="blue"), cslider(name="rndamp", label="Amp Rand Amp", min=0, max=1, init=0, rel="lin", unit="x", col="blue4", half=True), cslider(name="rnddelamp", label="Pitch Rand Amp", min=0, max=0.25, init=0, rel="lin", unit="x", col="blue2", half=True), cslider(name="rndspeed", label="Amp Rand Speed", min=0.001, max=100, init=0.25, rel="log", unit="Hz", col="blue4", half=True), cslider(name="rnddelspeed", label="Pitch Rand Speed", min=0.001, max=100, init=6, rel="log", unit="Hz", col="blue2", half=True), cslider(name="detune", label="Detune Factor", min=0.001, max=1, init=0.5, rel="lin", unit="x", col="orange"), cslider(name="voice1", label="Pitch Voice 1", min=1, max=130, init=60, rel="lin", unit="semi", half=True, col="green"), cslider(name="voice2", label="Pitch Voice 2", min=1, max=130, init=72, rel="lin", unit="semi", half=True, col="green"), cslider(name="voice3", label="Pitch Voice 3", min=1, max=130, init=84, rel="lin", unit="semi", half=True, col="green"), cslider(name="voice4", label="Pitch Voice 4", min=1, max=130, init=96, rel="lin", unit="semi", half=True, col="green"), cslider(name="voice5", label="Pitch Voice 5", min=1, max=130, init=48, rel="lin", unit="semi", half=True, col="green"), cslider(name="voice6", label="Pitch Voice 6", min=1, max=130, init=67, rel="lin", unit="semi", half=True, col="green"), cslider(name="voice7", label="Pitch Voice 7", min=1, max=130, init=36, rel="lin", unit="semi", half=True, col="green"), cslider(name="voice8", label="Pitch Voice 8", min=1, max=130, init=87, rel="lin", unit="semi", half=True, col="green"), cslider(name="fb", label="Feedback", min=0.01, max=0.9999, init=0.95, rel="lin", unit="x", col="red"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), ctoggle(name="onoffv1", label="Voice Activation ( 1 --> 8 )", init=1, stack=True, col="green"), ctoggle(name="onoffv2", label="", init=1, stack=True, col="green"), ctoggle(name="onoffv3", label="", init=1, stack=True, col="green"), ctoggle(name="onoffv4", label="", init=1, stack=True, col="green"), ctoggle(name="onoffv5", label="", init=1, stack=True, col="green"), ctoggle(name="onoffv6", label="", init=1, stack=True, col="green"), ctoggle(name="onoffv7", label="", init=1, stack=True, col="green"), ctoggle(name="onoffv8", label="", init=1, stack=True, col="green"), cpoly() ] cecilia5-5.4.1/Resources/modules/Resonators&Verbs/MatrixReverb.c5000066400000000000000000000433341372272363700247160ustar00rootroot00000000000000from math import sqrt, exp, e from random import sample def clipint(value, maxi): if value < 0: value = 0 elif value >= maxi: value = maxi - 1 return value def primes(mini=512, maxi=8192): ps, pp = [2, 3], 3 while pp < maxi: pp += 2 test = True sqrtpp = sqrt(pp) for a in ps: if a > sqrtpp: break if pp % a == 0: test = False break if test: ps.append(pp) return [x for x in ps if x > mini] def linmin(values, num): l = len(values) step = l // num v = [] for i in range(num): index = clipint(step * i, l) v.append(values[index]) return v def linmax(values, num): l = len(values) step = l // num last = l - 1 v = [] for i in range(num): index = clipint(last - step * i, l) v.append(values[index]) return v def expmin(values, num): l = len(values) v = [] for i in range(num): index = int((exp(i / num) - 1.0) / (e - 1.0) * l) index = clipint(index, l) v.append(values[index]) return v def expmax(values, num): l = len(values) last = l - 1 v = [] for i in range(num): index = last - int((exp(i / num) - 1.0) / (e - 1.0) * l) index = clipint(index, l) v.append(values[index]) return v def powmin(values, num): l = len(values) v = [] for i in range(num): index = int((pow(10.0, i / num) - 1.0) / 9.0 * l) index = clipint(index, l) v.append(values[index]) return v def powmax(values, num): l = len(values) last = l - 1 v = [] for i in range(num): index = last - int((pow(10.0, i / num) - 1.0) / 9.0 * l) index = clipint(index, l) v.append(values[index]) return v def sqrtmin(values, num): l = len(values) step = 1.0 / num v = [] for i in range(num): index = int(sqrt(step*i) * l) index = clipint(index, l) v.append(values[index]) return v def sqrtmax(values, num): l = len(values) step = 1.0 / num last = l - 1 v = [] for i in range(num): index = last - int(sqrt(step*i) * l) index = clipint(index, l) v.append(values[index]) return v def rand(values, num): return sample(values, num) def get_spacing_algorithm(algoname): dict = {"linmin": linmin, "linmax": linmax, "expmin": expmin, "expmax": expmax, "sqrtmin": sqrtmin, "sqrtmax": sqrtmax, "powmin": powmin, "powmax": powmax, "rand": rand} if algoname in dict.keys(): return dict[algoname] else: return dict["linmin"] class ERotate: def __init__(self, in1, in2, delay=0): self.in1, self.in2 = in1, in2 self.re = (self.in1 + self.in2) * 0.7071 self.im = SDelay(self.in1-self.in2, delay=delay, mul=0.7071) def setDelay(self, x): self.im.delay = x class Rotate2: def __init__(self, input): self.sig = [input[0] + input[1], input[0] - input[1]] class Rotate4: def __init__(self, input): self.h1, self.h2 = Rotate2(input[:2]), Rotate2(input[2:]) self.r1, self.i1 = self.h1.sig[0] + self.h2.sig[0], self.h1.sig[0] - self.h2.sig[0] self.r2, self.i2 = self.h1.sig[1] + self.h2.sig[1], self.h1.sig[1] - self.h2.sig[1] self.sig = [self.r1, self.r2, self.i1, self.i2] class Rotate8: def __init__(self, input): self.h1, self.h2 = Rotate4(input[:4]), Rotate4(input[4:]) self.r1, self.i1 = self.h1.sig[0] + self.h2.sig[0], self.h1.sig[0] - self.h2.sig[0] self.r2, self.i2 = self.h1.sig[1] + self.h2.sig[1], self.h1.sig[1] - self.h2.sig[1] self.r3, self.i3 = self.h1.sig[2] + self.h2.sig[2], self.h1.sig[2] - self.h2.sig[2] self.r4, self.i4 = self.h1.sig[3] + self.h2.sig[3], self.h1.sig[3] - self.h2.sig[3] self.sig = [self.r1, self.r2, self.r3, self.r4, self.i1, self.i2, self.i3, self.i4] class Rotate16: def __init__(self, input): self.h1, self.h2 = Rotate8(input[:8]), Rotate8(input[8:]) self.r1, self.i1 = self.h1.sig[0] + self.h2.sig[0], self.h1.sig[0] - self.h2.sig[0] self.r2, self.i2 = self.h1.sig[1] + self.h2.sig[1], self.h1.sig[1] - self.h2.sig[1] self.r3, self.i3 = self.h1.sig[2] + self.h2.sig[2], self.h1.sig[2] - self.h2.sig[2] self.r4, self.i4 = self.h1.sig[3] + self.h2.sig[3], self.h1.sig[3] - self.h2.sig[3] self.r5, self.i5 = self.h1.sig[4] + self.h2.sig[4], self.h1.sig[4] - self.h2.sig[4] self.r6, self.i6 = self.h1.sig[5] + self.h2.sig[5], self.h1.sig[5] - self.h2.sig[5] self.r7, self.i7 = self.h1.sig[6] + self.h2.sig[6], self.h1.sig[6] - self.h2.sig[6] self.r8, self.i8 = self.h1.sig[7] + self.h2.sig[7], self.h1.sig[7] - self.h2.sig[7] self.sig = [self.r1, self.r2, self.r3, self.r4, self.r5, self.r6, self.r7, self.r8, self.i1, self.i2, self.i3, self.i4, self.i5, self.i6, self.i7, self.i8] class LoP: def __init__(self, input, freq, damp, which, order=2): self._which = which if order not in [1, 2, 4]: order = 2 if which < 4: self.lp = {1: Tone, 2: ButLP, 4: MoogLP}[order](input, freq) self.sig = Sig(self.lp - input, mul=damp, add=input) else: self.sig = input def setFreq(self, x): if self._which < 4: self.lp.freq = x def setDamp(self, x): if self._which < 4: self.sig.mul = x class MatrixVerb(PyoObject): def __init__(self, input, liveness=0.7, depth=0.7, crossover=3500, highdamp=0.75, balance=0.25, numechoes=8, quality=4, filtorder=2, echoesrange=[0.03, 0.08], echoesmode="linmin", matrixrange=[0.05, 0.15], matrixmode="linmin", mul=1, add=0): PyoObject.__init__(self, mul, add) # Raw arguments so that we can retrieve them with the attribute syntax. self._input = input self._liveness = liveness # reverb time. self._depth = depth # balance early reflexions / reverb tail. self._crossover = crossover # lowpass cutoff frequency. self._highdamp = highdamp # high frequencies damping. self._balance = balance # balance dry / wet. # Get current sampling rate. sampleRate = Sig(0).getSamplingRate() # Normalization gain and number of delays for the rotating matrix. if quality < 1: quality = 1 elif quality > 4: quality = 4 num_delays = 2 ** quality # Compensation for the 4-order lowpass filter at high cutoff frequency. if filtorder == 4: gain = pow(10, -4.01 * quality * 0.05) else: gain = pow(10, -3.01 * quality * 0.05) # Computes early reflections delay times (ensure there is enough # prime numbers to satisfy numechoes). er_primes = primes(int(echoesrange[0] * sampleRate), int(echoesrange[1] * sampleRate)) extend = 0.005 while len(er_primes) < numechoes: er_primes = primes(int(echoesrange[0] * sampleRate), int((echoesrange[1] + extend) * sampleRate)) extend += 0.005 samps = get_spacing_algorithm(echoesmode)(er_primes, numechoes) self._er_delays = [x / sampleRate for x in samps] # Computes delay line lengths (ensure there is enough prime numbers # to satisfy num_delays). dl_primes = primes(int(matrixrange[0] * sampleRate), int(matrixrange[1] * sampleRate)) extend = 0.005 while len(dl_primes) < num_delays: dl_primes = primes(int(matrixrange[0] * sampleRate), int((matrixrange[1] + extend) * sampleRate)) extend += 0.005 samps = get_spacing_algorithm(matrixmode)(dl_primes, num_delays) self._delays = [x / sampleRate for x in samps] # Feedback based on liveness parameter and number of stages. self._feedback = Sig(liveness) self._clippedfeed = Clip(self._feedback, min=0, max=1, mul=gain) # Input crossfader and pre-lowpass filtering. self._in_fader = InputFader(input) self._prefilter = Tone(Denorm(self._in_fader.mix(1)), self._crossover) # Early reflexions as a sequence of ERotate objects. self._earlyrefs = [ERotate(self._prefilter, Sig(0), self._er_delays.pop(0))] for t in self._er_delays: self._earlyrefs.append(ERotate(self._earlyrefs[-1].re, self._earlyrefs[-1].im, t)) # First "buffersize" delay lines input signal (will be replaced by the matrix outputs). self._matrixin = [self._earlyrefs[-1].re, self._earlyrefs[-1].im] self._matrixin.extend([Sig(0) for i in range(num_delays-2)]) # Reverberation tail delay lines. self._dlines = [SDelay(self._matrixin[i], delay=self._delays[i]) for i in range(num_delays)] # Lowpass filtering. self._lopass = [LoP(self._dlines[i], self._crossover, self._highdamp, i, filtorder) for i in range(num_delays)] # Delay lines feedback + input. self._torotate = [self._lopass[i].sig * self._clippedfeed + self._matrixin[i] for i in range(num_delays)] # Select and apply the rotating matrix. self._matrix = {2: Rotate2, 4: Rotate4, 8: Rotate8, 16: Rotate16}[num_delays](self._torotate) # Feed the delay lines with the output of the rotation matrix. [self._dlines[i].setInput(self._matrix.sig[i]) for i in range(num_delays)] # Early reflections / reverberation tail balance and stereo mixing. self._left = Interp(self._matrixin[0] * 2 + self._matrix.sig[-2] * 0.1, self._matrixin[0] * 0.5 + self._matrix.sig[-2], self._depth) self._right = Interp(self._matrixin[1] * 2 + self._matrix.sig[-1] * 0.1, self._matrixin[1] * 0.5 + self._matrix.sig[-1], self._depth) self._stereo = Mix([self._left, self._right], voices=2, mul=0.25) # Dry / wet balance and output audio streams. self._out = Interp(self._in_fader.mix(2), self._stereo, self._balance) # Create the "_base_objs" attribute. This is the object's audio output. self._base_objs = self._out.getBaseObjects() def setInput(self, x, fadetime=0.05): self._input = x self._in_fader.setInput(x, fadetime) def setLiveness(self, x): self._liveness = x self._feedback.value = x def setDepth(self, x): self._depth = x self._left.interp = x self._right.interp = x def setCrossover(self, x): self._crossover = x self._prefilter.freq = x [obj.setFreq(x) for obj in self._lopass] def setHighdamp(self, x): self._highdamp = x [obj.setDamp(x) for obj in self._lopass] def setBalance(self, x): self._balance = x self._out.interp = x def ctrl(self, map_list=None, title=None, wxnoserver=False): self._map_list = [SLMap(0, 1, "lin", "liveness", self._liveness), SLMap(0, 1, "lin", "depth", self._depth), SLMap(100, 15000, "log", "crossover", self._crossover), SLMap(0, 1, "lin", "highdamp", self._highdamp), SLMap(0, 1, "lin", "balance", self._balance), SLMapMul(self._mul)] PyoObject.ctrl(self, map_list, title, wxnoserver) @property def input(self): return self._input @input.setter def input(self, x): self.setInput(x) @property def liveness(self): return self._liveness @liveness.setter def liveness(self, x): self.setLiveness(x) @property def depth(self): return self._depth @depth.setter def depth(self, x): self.setDepth(x) @property def crossover(self): return self._crossover @crossover.setter def crossover(self, x): self.setCrossover(x) @property def highdamp(self): return self._highdamp @highdamp.setter def highdamp(self, x): self.setHighdamp(x) @property def balance(self): return self._balance @balance.setter def balance(self, x): self.setBalance(x) class Module(BaseModule): """ "Delay-line rotating-matrix reverb inspired by Miller Puckette's rev3~ object" Description This reverb uses a delay line network with a rotating matrix to create a dense reverberation tail. Time range for early reflexions and reverberation delays can be specified as well as the kind of algorithm to use to distribute values inside the given ranges. This leaves room for lot of explorations. This object is a kind of reverberator laboratory, play with the many arguments to hear the different reverb colors you can create with it! Sliders # Liveness : Internal feedback value of the delay network. A value of 1 produces an infinite reverb. # Room Size : Balance between early reflexions and reverb tail. Values around 0.4 give small rooms while higher values give larger rooms. # Crossover : Crossover frequency in Hz. Frequencies above the crossover will be attenuated according to the `High Freq Damp` parameter. # High Freq Damp : High frequencies damping factor between 0 and 1. A value of 0 means equal reverb time at all frequencies and a value of 1 means almost nothing above the crossover frequency gets through. # Dry/Wet : Balance, in the range 0 to 1, between the dry (input) and the wet (reverberated) signals. # Early Refs : The number of early reflexions. # Echoes Range : The minimum and maximum delay times, in seconds, used to compute the early reflexions. # Matrix Range : The minimum and maximum delay times, in seconds, used to compute the reverberation tail. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Matrix Order : Defines the reverb tail density. The rotating matrix will contain `2 ** quality` delay lines. The higher the better reverb quality (and also more expensive). # Lowpass Order : The order of the IIR lowpass filter used in the feedback network. It can be 1 for a 6dB/oct, 2 for a 12db/oct or 4 for a 24dB/oct. # Echoes Mode : The distribution algorithm used to compute the early reflexions delay times. The algorithm choose the delay times in a list of prime numbers generated according to the `Echoes Range` values. # Matrix Mode : The distribution algorithm used to compute the reverberation tail delay times. The algorithm choose the delay times in a list of prime numbers generated according to the `Matrix Range` values. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") echoesRange = self.echoesrange.get(True) matrixRange = self.matrixrange.get(True) self.verb = MatrixVerb(self.snd, liveness=self.liveness, depth=self.depth, crossover=self.crossover, highdamp=self.highdamp, balance=self.balance, numechoes=int(self.earlyrefs.get()), quality=int(self.quality_value), filtorder=int(self.filtorder_value), echoesrange=echoesRange, echoesmode=self.echoesmode_value, matrixrange=matrixRange, matrixmode=self.matrixmode_value, mul=1) self.out = Mix(self.verb, voices=self.nchnls, mul=self.env) Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="liveness", label="Liveness", min=0, max=1, init=0.7, unit="%", col="red"), cslider(name="depth", label="Room Size", min=0, max=1, init=0.7, unit="x", col="red2"), cslider(name="crossover", label="Crossover", min=500, max=10000, init=4000, rel="log", unit="Hz", col="green1"), cslider(name="highdamp", label="High Freq Damp", min=0, max=1, init=0.7, unit="x", col="green2"), cslider(name="balance", label="Dry/Wet", min=0, max=1, init=0.3, unit="x", col="blue1"), cslider(name="earlyrefs", label="Early Refs", min=2, max=16, init=8, res="int", gliss=0, col="grey"), crange(name='echoesrange', label='Echoes Range', min=0.001, max=0.25, init=[0.03, 0.08], gliss=0, rel='log', unit='sec', col='grey'), crange(name='matrixrange', label='Matrix Range', min=0.005, max=0.5, init=[0.05, 0.15], gliss=0, rel='log', unit='sec', col='grey'), cpopup(name="quality", label="Matrix Order", value=['1', '2', '3', '4'], init='3', rate="i", col="grey"), cpopup(name="filtorder", label="Lowpass Order", value=['1', '2', '4'], init='2', rate="i", col="grey"), cpopup(name="echoesmode", label="Echoes Mode", init="linmin", rate="i", col="grey", value=["linmin", "linmax", "expmin", "expmax", "sqrtmin", "sqrtmax", "powmin", "powmax", "rand"]), cpopup(name="matrixmode", label="Matrix Mode", init="linmin", rate="i", col="grey", value=["linmin", "linmax", "expmin", "expmax", "sqrtmin", "sqrtmax", "powmin", "powmax", "rand"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Resonators&Verbs/Resonators.c5000066400000000000000000000165741372272363700244510ustar00rootroot00000000000000import random class Module(BaseModule): """ "Eight resonators with jitter control" Description This module implements a resonator (waveguide) bank with random controls. This waveguide model consists of one delay-line with a simple lowpass filtering and lagrange interpolation. Sliders # Pitch Offset : Base pitch of all the resonators # Amp Random Amp : Amplitude of the jitter applied on the resonators amplitude # Amp Random Speed : Frequency of the jitter applied on the resonators amplitude # Pitch Random Amp : Amplitude of the jitter applied on the resonators frequency # Pitch Random Speed : Frequency of the jitter applied on the resonators frequency # Pitch Voice 1 : Pitch of the first resonator # Pitch Voice 2 : Pitch of the second resonator # Pitch Voice 3 : Pitch of the third resonator # Pitch Voice 4 : Pitch of the fourth resonator # Pitch Voice 5 : Pitch of the fifth resonator # Pitch Voice 6 : Pitch of the sixth resonator # Pitch Voice 7 : Pitch of the seventh resonator # Pitch Voice 8 : Pitch of the eigth resonator # Feedback : Amount of resonators fed back in the resonators # Dry / Wet : Mix between the original signal and the resonators Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Voice Activation ( 1 --> 8 ) : Mute or unmute each of the eigth voices independently # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") chnls = len(self.snd) num = 8 * chnls self.defamp = Sig([0.005 for i in range(num)]) self.ra = Randi(min=1-self.rndamp, max=1+self.rndamp, freq=self.rndspeed*[random.uniform(.75,1.25) for i in range(num)]) self.rf = Randi(min=1-self.rnddelamp, max=1+self.rnddelamp, freq=self.rnddelspeed*[random.uniform(.95,1.05) for i in range(num)]) self.off_transpo = CentsToTranspo(self.offset) self.frs = self.duplicate([self.voice1, self.voice2, self.voice3, self.voice4, self.voice5, self.voice6, self.voice7, self.voice8], chnls) self.freqs = MToF(self.frs, mul=self.off_transpo) self.all_freqs = self.freqs*self.rf self.wgs = Waveguide(input=self.snd*0.05, freq=self.all_freqs, dur=60*self.fb, minfreq=20, mul=self.ra*self.defamp) self.deg = Interp(self.snd, self.wgs.mix(chnls), self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) self.onoffv1(self.onoffv1_value) self.onoffv2(self.onoffv2_value) self.onoffv3(self.onoffv3_value) self.onoffv4(self.onoffv4_value) self.onoffv5(self.onoffv5_value) self.onoffv6(self.onoffv6_value) self.onoffv7(self.onoffv7_value) self.onoffv8(self.onoffv8_value) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd def onoff(self, i, value): l = self.defamp.value l[i*2:i*2+2] = [value, value] self.defamp.value = l def onoffv1(self, value): self.onoff(0, value) def onoffv2(self, value): self.onoff(1, value) def onoffv3(self, value): self.onoff(2, value) def onoffv4(self, value): self.onoff(3, value) def onoffv5(self, value): self.onoff(4, value) def onoffv6(self, value): self.onoff(5, value) def onoffv7(self, value): self.onoff(6, value) def onoffv8(self, value): self.onoff(7, value) Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="offset", label="Pitch Offset", min=-2400, max=2400, init=0, rel="lin", unit="cnts", col="blue"), cslider(name="voice1", label="Pitch Voice 1", min=1, max=130, init=60, rel="lin", unit="semi", half=True, col="green2"), cslider(name="voice2", label="Pitch Voice 2", min=1, max=130, init=72, rel="lin", unit="semi", half=True, col="green2"), cslider(name="voice3", label="Pitch Voice 3", min=1, max=130, init=84, rel="lin", unit="semi", half=True, col="green2"), cslider(name="voice4", label="Pitch Voice 4", min=1, max=130, init=96, rel="lin", unit="semi", half=True, col="green2"), cslider(name="voice5", label="Pitch Voice 5", min=1, max=130, init=48, rel="lin", unit="semi", half=True, col="green2"), cslider(name="voice6", label="Pitch Voice 6", min=1, max=130, init=67, rel="lin", unit="semi", half=True, col="green2"), cslider(name="voice7", label="Pitch Voice 7", min=1, max=130, init=36, rel="lin", unit="semi", half=True, col="green2"), cslider(name="voice8", label="Pitch Voice 8", min=1, max=130, init=87, rel="lin", unit="semi", half=True, col="green2"), cslider(name="rndamp", label="Amp Rand Amp", min=0, max=1, init=0, rel="lin", unit="x", col="blue4", half=True), cslider(name="rnddelamp", label="Pitch Rand Amp", min=0, max=0.25, init=0, rel="lin", unit="x", col="blue2", half=True), cslider(name="rndspeed", label="Amp Rand Speed", min=0.001, max=100, init=0.25, rel="log", unit="Hz", col="blue4", half=True), cslider(name="rnddelspeed", label="Pitch Rand Speed", min=0.001, max=100, init=6, rel="log", unit="Hz", col="blue2", half=True), cslider(name="fb", label="Feedback", min=0.01, max=0.999, init=0.95, rel="lin", unit="x", col="red"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="balance", label = "Balance", init= "Off", col="blue", value=["Off","Compress", "Source"]), ctoggle(name="onoffv1", label="Voice Activation / 1 > 8", init=1, stack=True, col="green2"), ctoggle(name="onoffv2", label="", init=1, stack=True, col="green2"), ctoggle(name="onoffv3", label="", init=1, stack=True, col="green2"), ctoggle(name="onoffv4", label="", init=1, stack=True, col="green2"), ctoggle(name="onoffv5", label="", init=1, stack=True, col="green2"), ctoggle(name="onoffv6", label="", init=1, stack=True, col="green2"), ctoggle(name="onoffv7", label="", init=1, stack=True, col="green2"), ctoggle(name="onoffv8", label="", init=1, stack=True, col="green2"), cpoly() ] cecilia5-5.4.1/Resources/modules/Resonators&Verbs/WGuideBank.c5000066400000000000000000000114471372272363700242640ustar00rootroot00000000000000import random class Module(BaseModule): """ "Multiple waveguide models module" Description Resonators whose frequencies are controlled with an expansion expression. freq[i] = BaseFreq * pow(WGExpansion, i) Sliders # Base Freq : Base pitch of the waveguides # WG Expansion : Spread between waveguides # WG Feedback : Length of the waveguides # Filter Cutoff : Center frequency of the filter # Filter Q : Q (resonance) of the filter # Amp Dev Amp : Amplitude of the jitter applied on the waveguides amplitude # Amp Dev Speed : Frequency of the jitter applied on the waveguides amplitude # Freq Dev Amp : Amplitude of the jitter applied on the waveguides pitch # Freq Dev Speed : Frequency of the jitter applied on the waveguides pitch # Dry / Wet : Mix between the original signal and the waveguides Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of the post-process filter # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") chnls = len(self.snd) num = 8 * chnls self.ra = Randi(min=1-self.dev, max=1+self.dev, freq=self.speed*[random.uniform(0.85,1.15) for i in range(num)], mul=0.03) self.rf = Randi(min=1-self.fdev, max=1+self.fdev, freq=self.fspeed*[random.uniform(0.97,1.03) for i in range(num)]) self.voices = [self.basefreq*Pow(self.exp, i) for i in range(num)] self.frs = self.duplicate(self.voices, chnls) self.wgs = Waveguide(input=self.snd, freq=Dummy(self.frs)*self.rf, dur=60*self.fb, minfreq=10, mul=self.ra) self.wgsm = self.wgs.mix(chnls) self.biquad = Biquadx(self.wgsm, freq=self.filter, q=self.filterq, type=self.filttype_index, stages=2, mul=0.4) self.deg = Interp(self.snd, self.biquad, self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd def filttype(self, index, value): self.biquad.type = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="basefreq", label="Base Freq", min=10, max=1000, init=40, rel="log", unit="Hz", col="blue"), cslider(name="exp", label="WG Expansion", min=0, max=4, init=1.33, rel="lin", unit="x", col="blue4"), cslider(name="fb", label="WG Feedback", min=0, max=0.999, init=0.95, rel="lin", unit="x", col="blue4"), cslider(name="filter", label="Filter Cutoff", min=50, max=20000, init=20000, rel="log", unit="Hz", col="green1"), cslider(name="filterq", label="Filter Q", min=0.5, max=10, init=0.7, rel="log", unit="x", col="green2"), cslider(name="dev", label="Amp Dev Amp", min=0.001, max=1, init=0.01, rel="log", unit="x", col="purple2", half = True), cslider(name="fdev", label="Freq Dev Amp", min=0.001, max=1, init=0.01, rel="log", unit="x", col="purple1", half = True), cslider(name="speed", label="Amp Dev Speed", min=0.01, max=120, init=1, rel="log", unit="Hz", col="purple2", half = True), cslider(name="fspeed", label="Freq Dev Speed", min=0.01, max=120, init=1, rel="log", unit="Hz", col="purple1", half = True), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="filttype", label="Filter Type", init="Lowpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/000077500000000000000000000000001372272363700204125ustar00rootroot00000000000000cecilia5-5.4.1/Resources/modules/Spectral/AddResynth.c5000066400000000000000000000076511372272363700227210ustar00rootroot00000000000000class Module(BaseModule): """ "Phase vocoder additive resynthesis" Description This module uses the magnitudes and true frequencies of a phase vocoder analysis to control amplitudes and frequencies envelopes of an oscillator bank. Sliders # Transpo : Transposition factor # Num of Oscs : Number of oscillators used to synthesize the sound # First bin : First synthesized bin # Increment : Step between synthesized bins # Dry / Wet : Mix between the original signal and the processed signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr self.delsrc = Delay(self.snd, delay=size*self.oneOverSr) self.fin = PVAnal(self.snd, size=size, overlaps=olaps, wintype=wintype) self.fout = PVAddSynth(self.fin, pitch=self.transpo, num=int(self.num.get()), first=int(self.first.get()), inc=int(self.inc.get())) self.fade = SigTo(value=1, time=.05, init=1) self.out = Interp(self.delsrc*0.7, self.fout, self.mix, mul=self.fade*self.env) def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = newsize*self.oneOverSr self.fin.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin.wintype = index def num_up(self, value): self.fout.num = int(value) def first_up(self, value): self.fout.first = int(value) def inc_up(self, value): self.fout.inc = int(value) Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="transpo", label="Transpo", min=0.25, max=4, init=1, rel="lin", unit="x", col="green1"), cslider(name="num", label="Num of Oscs", min=1, max=500, init=100, rel="lin", res="int", unit="x", up=True), cslider(name="first", label="First bin", min=0, max=50, init=0, rel="lin", res="int", unit="x", up=True), cslider(name="inc", label="Increment", min=1, max=25, init=1, rel="lin", res="int", unit="x", up=True), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/BinModulator.c5000066400000000000000000000150451372272363700232470ustar00rootroot00000000000000class Module(BaseModule): """ "Frequency independent amplitude and frequency modulations" Description This module modulates both the amplitude and the frequency of each bin from a phase vocoder analysis with an independent oscillator. Power series are used to compute modulating oscillator frequencies. Sliders # AM Base Freq : Base amplitude modulation frequency, in Hertz. # AM Spread : Spreading factor for AM oscillator frequencies. 0 means every oscillator has the same frequency. # FM Base Freq : Base frequency modulation frequency, in Hertz. # FM Spread : Spreading factor for FM oscillator frequencies. 0 means every oscillator has the same frequency. # FM Depth : Amplitude of the modulating oscillators. # Dry / Wet : Mix between the original signal and the delayed signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Reset : On mouse down, this button reset the phase of all bin's oscillators to 0. # Routing : Path of the sound # AM Shape : Waveform of the amplitude modulators. Possible shapes are: Sine, Sawtooth, Ramp, Square, Triangle, Brown Noise, Pink Noise, White Noise # FM Shape : Waveform of the frequency modulators. Possible shapes are: Sine, Sawtooth, Ramp, Square, Triangle, Brown Noise, Pink Noise, White Noise # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr routing = self.routing_index self.delsrc = Delay(self.snd, delay=size*self.oneOverSr) self.fin = PVAnal(self.snd, size=size, overlaps=olaps, wintype=wintype) self.tr1 = PVAmpMod(self.fin, basefreq=self.amfreq, spread=self.amspread, shape=self.ampmodshape_index) self.tr2 = PVFreqMod(self.tr1, basefreq=self.fmfreq, spread=self.fmspread, depth=self.fmdepth, shape=self.freqmodshape_index) self.fout = PVSynth(self.tr2, wintype=wintype) self.fade = SigTo(value=1, time=.05, init=1) self.out = Interp(self.delsrc*0.5, self.fout*0.4, self.mix, mul=self.fade*self.env) self.setRouting(routing) def setRouting(self, x): if x == 0: self.tr1.input = self.fin self.fout.input = self.tr1 self.tr1.play() self.tr2.stop() elif x == 1: self.tr2.input = self.fin self.fout.input = self.tr2 self.tr1.stop() self.tr2.play() else: self.tr1.input = self.fin self.tr2.input = self.tr1 self.fout.input = self.tr2 self.tr1.play() self.tr2.play() def routing(self, index, value): self.setRouting(index) def ampmodshape(self, index, value): self.tr1.shape = index def freqmodshape(self, index, value): self.tr2.shape = index def reset(self, value): if value: if self.tr1.isPlaying(): self.tr1.reset() if self.tr2.isPlaying(): self.tr2.reset() def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = newsize*self.oneOverSr self.fin.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="amfreq", label="AM Base Freq", min=0.0001, max=100, init=4, rel="log", unit="Hz", col="green1"), cslider(name="amspread", label="AM Spread", min=-1, max=1, init=0.5, rel="lin", unit="x", col="green2"), cslider(name="fmfreq", label="FM Base Freq", min=0.0001, max=100, init=2, rel="log", unit="Hz", col="orange1"), cslider(name="fmspread", label="FM Spread", min=-1, max=1, init=0.1, rel="lin", unit="x", col="orange2"), cslider(name="fmdepth", label="FM Depth", min=0, max=1, init=0.1, rel="lin", unit="x", col="orange3"), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cbutton(name="reset", label="Reset", col="blue2"), cpopup(name="routing", label="Routing", init="Amp Only", value=["Amp Only", "Freq Only", "Amp -> Freq"], col="blue2"), cpopup(name="ampmodshape", label="AM Shape", init="Sine", value=["Sine", "Sawtooth", "Ramp", "Square", "Triangle", "Brown Noise", "Pink Noise", "White Noise"], col="green1"), cpopup(name="freqmodshape", label="FM Shape", init="Sine", value=["Sine", "Sawtooth", "Ramp", "Square", "Triangle", "Brown Noise", "Pink Noise", "White Noise"], col="orange1"), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/BinWarper.c5000066400000000000000000000067211372272363700225420ustar00rootroot00000000000000class Module(BaseModule): """ "Phase vocoder buffer with bin independent speed playback" Description This module pre-analyses the input sound and keeps the phase vocoder frames in a buffer for the playback. User has control on playback position independently for every frequency bin. Sliders # Low Bin Speed : Lowest bin speed factor # High Bin Speed : Highest bin speed factor * For random distribution, these values are the minimum and the maximum of the distribution. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Reset : Reset pointer positions to the beginning of the buffer. # Speed Distribution : Speed distribution algorithm. # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") snddur = self.getSamplerDur("snd") self.server.startoffset = snddur size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index mode = self.mode_index self.fin = PVAnal(self.snd, size=size, overlaps=olaps, wintype=wintype) self.buf = PVBufLoops(self.fin, self.low, self.high, mode, length=snddur) self.fout = PVSynth(self.buf, wintype=wintype, mul=self.env) self.fade = SigTo(value=1, time=.05, init=1) self.out = Sig(self.fout, mul=self.fade) def reset(self, value): if value: self.buf.reset() def mode(self, index, value): self.buf.mode = index def wtype(self, index, value): self.fin.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="low", label="Low Bin Speed", min=0, max=2, init=0.9, rel="lin", unit="x", col="green1"), cslider(name="high", label="High Bin Speed", min=0, max=2, init=1.1, rel="lin", unit="x", col="green2"), cbutton(name="reset", label="Reset", col="blue2"), cpopup(name="mode", label="Speed Distribution", init="Linear", col="blue2", value=["Linear", "Exponential", "Logarithmic", "Random", "Rand Exp Min", "Rand Exp Max", "Rand Bi-Exp"]), cpopup(name="fftsize", label="FFT Size", init="1024", rate="i", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", rate="i", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/CrossSynth.c5000066400000000000000000000100571372272363700227650ustar00rootroot00000000000000class Module(BaseModule): """ "Phase Vocoder based cross synthesis module" Description Multiplication of magnitudes from two phase vocoder streaming objects. Sliders # Exciter Pre Filter Freq : Frequency of the pre-FFT filter # Exciter Pre Filter Q : Q of the pre-FFT filter # Dry / Wet : Mix between the original signal and the processed signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Exc Pre Filter Type : Type of the pre-FFT filter # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd1 = self.addSampler("snd1") self.snd2 = self.addSampler("snd2") self.snd2_filt = Biquadx(self.snd2, freq=self.prefiltf, q=self.prefiltq, type=self.prefilttype_index, stages=2) size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr self.delsrc = Delay(self.snd1, delay=size*self.oneOverSr) self.fin1 = PVAnal(self.snd1, size=size, overlaps=olaps, wintype=wintype) self.fin2 = PVAnal(self.snd2_filt, size=size, overlaps=olaps, wintype=wintype) self.cross = PVMult(self.fin1, self.fin2) self.fout = PVSynth(self.cross, wintype=wintype, mul=4) self.fade = SigTo(value=1, time=.03, init=1) self.out = Interp(self.delsrc*self.env*0.5, self.fout*self.env, self.mix, mul=self.fade) def prefilttype(self, index, value): self.snd2_filt.type = index def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = newsize*self.oneOverSr self.fin1.size = newsize self.fin2.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin1.overlaps = olaps self.fin2.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin1.wintype = index self.fin2.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd1", label="Spectral Envelope"), csampler(name="snd2", label="Source Exciter"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="prefiltf", label="Exc Pre Filter Freq", min=40, max=18000, init=150, rel="log", unit="Hz", col="green"), cslider(name="prefiltq", label="Exc Pre Filter Q", min=.5, max=10, init=0.707, rel="log", col="green"), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="prefilttype", label="Exc Pre Filter Type", init="Highpass", col="green", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/CrossSynth2.c5000066400000000000000000000106341372272363700230500ustar00rootroot00000000000000class Module(BaseModule): """ "Phase Vocoder based cross synthesis module" Description Performs cross-synthesis between two phase vocoder streaming objects. The amplitudes from `Source Exciter` and `Spectral Envelope`, scaled by `Crossing Index`, are applied to the frequencies of `Source Exciter`. Sliders # Crossing Index : Morphing index between the two source's magnitudes # Exc Pre Filter Freq : Frequency of the pre-FFT filter # Exc Pre Filter Q : Q of the pre-FFT filter # Dry / Wet : Mix between the original signal and the processed signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Exc Pre Filter Type : Type of the pre-FFT filter # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd1 = self.addSampler("snd1") self.snd2 = self.addSampler("snd2") self.snd2_filt = Biquadx(self.snd2, freq=self.prefiltf, q=self.prefiltq, type=self.prefilttype_index, stages=2) size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr self.delsrc = Delay(self.snd1, delay=size*self.oneOverSr) self.fin1 = PVAnal(self.snd1, size=size, overlaps=olaps, wintype=wintype) self.fin2 = PVAnal(self.snd2_filt, size=size, overlaps=olaps, wintype=wintype) self.cross = PVCross(self.fin2, self.fin1, self.interp) self.fout = PVSynth(self.cross, wintype=wintype, mul=1) self.fade = SigTo(value=1, time=.03, init=1) self.out = Interp(self.delsrc*self.env*0.5, self.fout*self.env, self.mix, mul=self.fade) def prefilttype(self, index, value): self.snd2_filt.type = index def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = newsize*self.oneOverSr self.fin1.size = newsize self.fin2.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin1.overlaps = olaps self.fin2.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin1.wintype = index self.fin2.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd1", label="Spectral Envelope"), csampler(name="snd2", label="Source Exciter"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="interp", label="Crossing Index", min=0, max=1, init=1, rel="lin", unit="x", col="red"), cslider(name="prefiltf", label="Exc Pre Filter Freq", min=40, max=18000, init=150, rel="log", unit="Hz", col="green"), cslider(name="prefiltq", label="Exc Pre Filter Q", min=.5, max=10, init=0.707, rel="log", col="green"), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="prefilttype", label="Exc Pre Filter Type", init="Highpass", col="green", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/Morphing.c5000066400000000000000000000071261372272363700224340ustar00rootroot00000000000000class Module(BaseModule): """ "Phase Vocoder based morphing module" Description This module performs spectral morphing between two phase vocoder analysis. According to `Morphing Index`, the amplitudes from two PV analysis are interpolated linearly while the frequencies are interpolated exponentially. Sliders # Morphing Index : Morphing index between the two sources # Dry / Wet : Mix between the original signal and the morphed signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd1 = self.addSampler("snd1", amp=.7) self.snd2 = self.addSampler("snd2", amp=.7) size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr self.delsrc = Delay(self.snd1, delay=size*self.oneOverSr) self.fin1 = PVAnal(self.snd1, size=size, overlaps=olaps, wintype=wintype) self.fin2 = PVAnal(self.snd2, size=size, overlaps=olaps, wintype=wintype) self.morph = PVMorph(self.fin1, self.fin2, self.interp) self.fout = PVSynth(self.morph, wintype=wintype) self.fade = SigTo(value=1, time=.05, init=1) self.out = Interp(self.delsrc*self.env*0.5, self.fout*self.env, self.mix, mul=self.fade) def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = newsize*self.oneOverSr self.fin1.size = newsize self.fin2.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin1.overlaps = olaps self.fin2.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin1.wintype = index self.fin2.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd1", label="Source 1"), csampler(name="snd2", label="Source 2"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="interp", label="Morphing Index", min=0, max=1, init=0.5, rel="lin", unit="x", col="green"), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/Shifter.c5000066400000000000000000000066021372272363700222530ustar00rootroot00000000000000class Module(BaseModule): """ "Phase Vocoder based frequency shifters" Description This module implements two voices that linearly moves the analysis bins by the amount, in Hertz, specified by the the `Shift 1` and `Shift 2` parameters. Sliders # Shift 1 : Shifting, in Hertz, of the first voice # Shift 2 : Shifting, in Hertz, of the second voice # Dry / Wet : Mix between the original signal and the delayed signals Popups & Toggles # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr self.delsrc = Delay(self.snd, delay=size*self.oneOverSr) self.fin = PVAnal(self.snd, size=size, overlaps=olaps, wintype=wintype) self.tr1 = PVShift(self.fin, shift=self.shift1) self.tr2 = PVShift(self.fin, shift=self.shift2) self.fout1 = PVSynth(self.tr1, wintype=wintype) self.fout2 = PVSynth(self.tr2, wintype=wintype) self.fout = self.fout1 + self.fout2 self.fade = SigTo(value=1, time=.05, init=1) self.out = Interp(self.delsrc*0.5, self.fout*0.4, self.mix, mul=self.fade*self.env) def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = newsize*self.oneOverSr self.fin.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="shift1", label="Shift 1", min=-8000, max=8000, init=250, rel="lin", unit="Hz", col="green1"), cslider(name="shift2", label="Shift 2", min=-8000, max=8000, init=500, rel="lin", unit="Hz", col="green2"), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=0.5, rel="lin", unit="x", col="blue"), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/SpectralDelay.c5000066400000000000000000000103221372272363700233750ustar00rootroot00000000000000class Module(BaseModule): """ "Phase vocoder spectral delay" Description This module applies different delay times and feedbacks for each bin of a phase vocoder analysis. Delay times and feedbacks are specified via grapher functions. Sliders # Max Delay : Maximum delay time per bin. Used to allocate memories. only available at initialization time # Dry / Wet : Mix between the original signal and the delayed signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Bin Delays : Controls the delay time for every bin, from left (low freq) to right (high freq) on the graph # Bin Feedbacks : Controls the feedback factor for every bin, from left to right on the graph Popups & Toggles # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.size = int(self.fftsize_value) self.olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr frames = int(self.maxd.get() * self.sr / (self.size / self.olaps)) self.dtab = DataTable(size=8192) self.tabscl = TableScale(self.delays, self.dtab, mul=frames) self.delsrc = Delay(self.snd, delay=self.size*self.oneOverSr) self.fin = PVAnal(self.snd, size=self.size, overlaps=self.olaps, wintype=wintype) self.dls = PVDelay(self.fin, self.dtab, self.feeds, maxdelay=self.maxd.get(), mode=1) self.fout = PVSynth(self.dls, wintype=wintype, mul=self.env) self.fade = SigTo(value=1, time=.05, init=1) self.out = Interp(self.delsrc*self.env, self.fout, self.mix, mul=self.fade) def fftsize(self, index, value): self.size = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = self.size*self.oneOverSr self.fin.size = self.size frames = int(self.maxd.get() * self.sr / (self.size / self.olaps)) self.tabscl.mul = frames time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): self.olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin.overlaps = self.olaps frames = int(self.maxd.get() * self.sr / (self.size / self.olaps)) self.tabscl.mul = frames time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin.wintype = index self.fout.wintype = index def maxd_up(self, value): pass Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cgraph(name="delays", label="Bin Delays", table=True, size=8192, func=[(0,0),(1,0.5)], col="green"), cgraph(name="feeds", label="Bin Feedbacks", table=True, size=8192, func=[(0,0.25),(1,0.25)], col="orange"), cslider(name="maxd", label="Max delay", min=0.1, max=20, init=5, rel="lin", unit="secs", up=True), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="fftsize", label="FFT Size", init="1024", value=["64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/SpectralFilter.c5000066400000000000000000000064001372272363700235660ustar00rootroot00000000000000class Module(BaseModule): """ "Phase Vocoder based filter" Description This module filters frequency components of a phase vocoder analysed sound according to the shape drawn in the grapher function. Sliders # Dry / Wet : Mix between the original signal and the processed signal Graph Only # Spectral Filter : Shape of the filter (amplitude of analysis bins) # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr self.delsrc = Delay(self.snd, delay=size*self.oneOverSr) self.fin = PVAnal(self.snd, size=size, overlaps=olaps, wintype=wintype) self.filt = PVFilter(self.fin, self.filter_table, mode=1) self.fout = PVSynth(self.filt, wintype=wintype, mul=self.env) self.fade = SigTo(value=1, time=.05, init=1) self.out = Interp(self.delsrc*self.env, self.fout, self.mix, mul=self.fade) def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = newsize*self.oneOverSr self.fin.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd"), cgraph(name="filter_table", label="Spectral Filter", table=True, size=8192, func=[(0,0),(0.05,1),(0.1,0),(0.2,0),(0.3,.7),(0.4,0),(0.5,0),(0.6,.5),(0.7,0),(1,0)], col="green"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/SpectralGate.c5000066400000000000000000000060351372272363700232250ustar00rootroot00000000000000class Module(BaseModule): """ "Spectral gate (Phase Vocoder)" Description For each frequency band of a phase vocoder analysis, if the amplitude of the bin falls below a given threshold, it is attenuated according to the `Gate Attenuation` parameter. Sliders # Gate Threshold : dB value at which the gate becomes active # Gate Attenuation : Gain in dB of the gated signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.fin = PVAnal(self.snd, size=size, overlaps=olaps, wintype=wintype) self.gate = PVGate(self.fin, thresh=self.gthresh, damp=DBToA(self.gatt)) self.fout = PVSynth(self.gate, wintype=wintype, mul=self.env) self.fade = SigTo(value=1, time=.05, init=1) self.out = Sig(self.fout, mul=self.fade) def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.fin.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="gthresh", label="Gate Threshold", min=-120, max=0, init=-30, rel="lin", unit="db", col="orange"), cslider(name="gatt", label="Gate Attenuation", min=-120, max=0, init=-120, rel="lin", unit="db", col="purple1"), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/SpectralWarper.c5000066400000000000000000000054321372272363700236050ustar00rootroot00000000000000class Module(BaseModule): """ "Phase Vocoder buffer and playback with transposition" Description This module pre-analyses the input sound and keeps the phase vocoder frames in a buffer for the playback. User has control on playback position and transposition. Sliders # Position : Normalized position (0 -> 1) inside the recorded PV buffer. Buffer length is the same as the sound duration. # Transposition : Pitch of the playback sound. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") snddur = self.getSamplerDur("snd") size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.fin = PVAnal(self.snd, size=size, overlaps=olaps, wintype=wintype) self.buf = PVBuffer(self.fin, self.pos, CentsToTranspo(self.pitch), length=snddur) self.fout = PVSynth(self.buf, wintype=wintype, mul=self.env) self.fade = SigTo(value=1, time=.05, init=1) self.out = Sig(self.fout, mul=self.fade) def wtype(self, index, value): self.fin.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="pos", label="Position", min=0, max=1, init=0, func=[(0,0), (1,1)], rel="lin", unit="x", col="green"), cslider(name="pitch", label="Transposition", min=-2400, max=2400, init=0, rel="lin", unit="cts", col="blue"), cpopup(name="fftsize", label="FFT Size", init="1024", rate="i", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", rate="i", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/Transpose.c5000066400000000000000000000067701372272363700226330ustar00rootroot00000000000000class Module(BaseModule): """ "Phase Vocoder based two voices transposer" Description This module transpose the frequency components of a phase vocoder analysis. Sliders # Transpo 1 : Transposition factor of the first voice # Transpo 2 : Transposition factor of the second voice # Dry / Wet : Mix between the original signal and the delayed signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr self.delsrc = Delay(self.snd, delay=size*self.oneOverSr) self.fin = PVAnal(self.snd, size=size, overlaps=olaps, wintype=wintype) self.tr1 = PVTranspose(self.fin, transpo=CentsToTranspo(self.transpo1)) self.tr2 = PVTranspose(self.fin, transpo=CentsToTranspo(self.transpo2)) self.fout1 = PVSynth(self.tr1, wintype=wintype) self.fout2 = PVSynth(self.tr2, wintype=wintype) self.fout = self.fout1 + self.fout2 self.fade = SigTo(value=1, time=.05, init=1) self.out = Interp(self.delsrc*0.5, self.fout*0.4, self.mix, mul=self.fade*self.env) def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = newsize*self.oneOverSr self.fin.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="transpo1", label="Transpo 1", min=-2400, max=2400, init=-200, rel="lin", unit="cts", col="green1"), cslider(name="transpo2", label="Transpo 2", min=-2400, max=2400, init=300, rel="lin", unit="cts", col="green2"), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=0.5, rel="lin", unit="x", col="blue"), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", col="red", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Spectral/Vectral.c5000066400000000000000000000073771372272363700222610ustar00rootroot00000000000000class Module(BaseModule): """ "Phase Vocoder based vectral module (spectral gate and verb)" Description This module implements a spectral gate followed by a spectral reverb. Sliders # Gate Threshold : dB value at which the gate becomes active # Gate Attenuation : Gain in dB of the gated signal # Time Factor : Filter coefficient for decreasing bins # High Freq Damping : High frequencies damping factor # Dry / Wet : Mix between the original signal and the delayed signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # FFT Size : Size, in samples, of the FFT # FFT Envelope : Windowing shape of the FFT # FFT Overlaps : Number of FFT overlaping analysis # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") size = int(self.fftsize_value) olaps = int(self.overlaps_value) wintype = self.wtype_index self.oneOverSr = 1.0 / self.sr self.delsrc = Delay(self.snd, delay=size*self.oneOverSr) self.fin = PVAnal(self.snd, size=size, overlaps=olaps, wintype=wintype) self.gate = PVGate(self.fin, self.gthresh, DBToA(self.gatt)) self.vect = PVVerb(self.gate, self.downfac, self.damp) self.fout = PVSynth(self.vect, wintype=wintype, mul=self.env) self.fade = SigTo(value=1, time=.05, init=1) self.out = Interp(self.delsrc*self.env, self.fout, self.mix, mul=self.fade) def fftsize(self, index, value): newsize = int(value) self.fade.value = 0 time.sleep(.05) self.delsrc.delay = newsize*self.oneOverSr self.fin.size = newsize time.sleep(.05) self.fade.value = 1 def overlaps(self, index, value): olaps = int(value) self.fade.value = 0 time.sleep(.05) self.fin.overlaps = olaps time.sleep(.05) self.fade.value = 1 def wtype(self, index, value): self.fin.wintype = index self.fout.wintype = index Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="gthresh", label="Gate Threshold", min=-120, max=0, init=-30, rel="lin", unit="db", col="red"), cslider(name="gatt", label="Gate Attenuation", min=-120, max=0, init=-120, rel="lin", unit="db", col="red"), cslider(name="downfac", label="Time Factor", min=0, max=1, init=0.3, rel="lin", unit="x", col="orange"), cslider(name="damp", label="High Freq Damping", min=0, max=1, init=0.9, rel="lin", unit="x", col="green"), cslider(name="mix", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue"), cpopup(name="fftsize", label="FFT Size", init="1024", value=["16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"], col="red"), cpopup(name="wtype", label="FFT Envelope", init="Hanning", col="red", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="overlaps", label="FFT Overlaps", init="4", value=["1", "2", "4", "8", "16"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Synthesis/000077500000000000000000000000001372272363700206265ustar00rootroot00000000000000cecilia5-5.4.1/Resources/modules/Synthesis/AdditiveSynth.c5000066400000000000000000000103621372272363700236400ustar00rootroot00000000000000import random class Module(BaseModule): """ "Additive synthesis module" Description An all featured additive synthesis module. Sliders # Base Frequency : Base pitch of the synthesis # Partials Spread : Distance between partials # Partials Freq Rand Amp : Amplitude of the jitter applied on the partials pitch # Partials Freq Rand Speed : Frequency of the jitter applied on the partials pitch # Partials Amp Rand Amp : Amplitude of the jitter applied on the partials amplitude # Partials Amp Rand Speed : Frequency of the jitter applied on the partials amplitude # Amplitude Factor : Spread of amplitude between partials # Chorus Depth : Amplitude of the chorus # Chorus Feedback : Amount of chorused signal fed back to the chorus # Chorus Dry / Wet : Mix between the original synthesis and the chorused signal Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Num of Partials : Number of partials present # Wave Shape : Shape used for the synthesis # Custom Wave : Define a custom wave shape by entering amplitude values """ def __init__(self): BaseModule.__init__(self) self.customtable = self.custom_value self.wavetable = HarmTable(size=8192) self.polyfreqs = self.polyphony_spread self.ply = [i*self.freq for i in self.polyfreqs for j in range(self.nchnls)] self.out = OscBank(table=self.wavetable, freq=self.ply, spread=self.spread, slope=self.ampfactor, fjit=True, frndf=self.rndampspeedf, frnda=self.rndampf, arndf=self.rndampspeed, arnda=self.rndamp, num=int(self.num_value), mul=self.polyphony_scaling*self.env) #INIT self.wavedict = {'Sine':[1], 'Sawtooth':[1, 0.5, 0.333, 0.25, 0.2, 0.167, 0.143, 0.111, 0.1], 'Square':[1, 0, 0.333, 0, 0.2, 0, 0.143, 0, 0.111], 'Complex1':[1, 0, 0, 0, 0.3, 0, 0, 0, 0.2, 0, 0, 0.05], 'Complex2':[1, 0, 0, 0.3, 0, 0, 0.2, 0, 0, 0, 0, 0.1, 0, 0, 0.05, 0, 0, 0.02], 'Complex3':[1, 0, 0, 0.2, 0, 0.1, 0, 0, 0, 0.3, 0, 0.1, 0, 0, 0.05, 0, 0, 0.1, 0, 0.05, 0, 0, 0, 0.05, 0, 0, 0.02], 'Custom':self.customtable} self.shape(self.shape_index, self.shape_value) def shape(self, index, value): self.wavetable.replace(self.wavedict[value]) def custom(self, value): self.wavedict['Custom'] = value Interface = [ cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="freq", label="Base Frequency", min=10, max=10000, init=150, rel="log", unit="Hz", col="blue"), cslider(name="spread", label="Partials Spread", min=0.0001, max=4, init=0.003, rel="log", unit="x", col="red3"), cslider(name="rndampspeedf", label=" Freq Rand Speed", min=0.0001, max=100, init=1, rel="log", unit="Hz", col="blue3",half=True), cslider(name="rndampspeed", label=" Amp Rand Speed", min=0.0001, max=20, init=1, rel="log", unit="Hz", col="green3",half=True), cslider(name="rndampf", label=" Freq Rand Amp", min=0.0001, max=1, init=0.02, rel="log", unit="x", col="blue3",half=True), cslider(name="rndamp", label=" Amp Rand Amp", min=0.0001, max=1, init=0.01, rel="log", unit="x", col="green3",half=True), cslider(name="ampfactor", label="Amplitude Factor", min=0.5, max=1, init=0.85, rel="lin", unit="x", col="green"), cpopup(name="num", label="Num of Partials", init="20", col="grey", rate="i", value=["5","10","20","40","80","160","320","640"]), cpopup(name="shape", label="Wave Shape", init="Square", col="green", value=["Sine","Sawtooth","Square","Complex1", "Complex2", "Complex3", "Custom"]), cgen(name="custom", label="Custom Wave", init=[1,0,0.5,0.3,0,0,0.2,0,0.1,0,0.09,0,0.05], popup=("shape", 6), col="green2"), cpoly() ] cecilia5-5.4.1/Resources/modules/Synthesis/Pulsar.c5000066400000000000000000000073041372272363700223310ustar00rootroot00000000000000import random class Module(BaseModule): """ "Pulsar synthesis module" Description This module implements the classic pulsar synthesis. Sliders # Base Frequency : Base pitch of the synthesis # Pulsar Width : Amount of silence added to one period # Detune Factor : Amount of jitter applied to the pitch # Detune Speed : Speed of the jitter applied to the pitch Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Wave Shape : Shape used for the synthesis # Custom Wave : Define a custom wave shape by entering amplitude values # Window Type : Pulsar envelope """ def __init__(self): BaseModule.__init__(self) self.customtable = self.custom_value self.wavetable = HarmTable(size=8192) self.wind = WinTable(type=self.wtype_index, size=8192) self.rnd1 = Randi(min=1-self.detune, max=1+self.detune, freq=self.detunesp) self.rnd2 = Randi(min=1-self.detune, max=1+self.detune, freq=self.detunesp) self.polyfreqs = self.polyphony_spread self.ply1 = [self.bfreq*i*self.rnd1 for i in self.polyfreqs] self.ply2 = [self.bfreq*i*self.rnd2 for i in self.polyfreqs] self.ply3 = [self.bfreq*i for i in self.polyfreqs for j in range(self.nchnls)] self.pfreqs = self.ply3+self.ply1+self.ply2 self.pul = Pulsar(self.wavetable, self.wind, freq=self.pfreqs, frac=self.width, phase=0, interp=4, mul=0.1*self.polyphony_scaling*self.env) self.out = Mix(self.pul, voices=self.nchnls) #INIT self.wavedict = {'Sine':[1], 'Sawtooth':[1, 0.5, 0.333, 0.25, 0.2, 0.167, 0.143, 0.111, 0.1], 'Square':[1, 0, 0.333, 0, 0.2, 0, 0.143, 0, 0.111], 'Complex1':[1, 0, 0, 0, 0.3, 0, 0, 0, 0.2, 0, 0, 0.05], 'Complex2':[1, 0, 0, 0.3, 0, 0, 0.2, 0, 0, 0, 0, 0.1, 0, 0, 0.05, 0, 0, 0.02], 'Complex3':[1, 0, 0, 0.2, 0, 0.1, 0, 0, 0, 0.3, 0, 0.1, 0, 0, 0.05, 0, 0, 0.1, 0, 0.05, 0, 0, 0, 0.05, 0, 0, 0.02], 'Custom':self.customtable} self.shape(self.shape_index, self.shape_value) def shape(self, index, value): self.wavetable.replace(self.wavedict[value]) def custom(self, value): self.wavedict['Custom'] = value def wtype(self, index, value): self.wind.type = index Interface = [ cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="bfreq", label="Base Frequency", min=0.1, max=1000, init=100, rel="log", unit="Hz", col="blue"), cslider(name="width", label="Pulsar Width", min=0.01, max=1, init=0.5, rel="lin", unit="x", col="green4"), cslider(name="detune", label="Detune Factor", min=0.0001, max=0.999, init=0.005, rel="log", unit="x", col="red"), cslider(name="detunesp", label="Detune Speed", min=0.0001, max=100, init=0.3, rel="log", unit="Hz", col="red"), cpopup(name="wtype", label="Window Type", init="Tuckey", col="orange4", value=["Rectangular", "Hamming", "Hanning", "Bartlett", "Blackman 3", "Blackman 4", "Blackman 7", "Tuckey", "Sine"]), cpopup(name="shape", label="Wave Shape", init="Sine", col="green", value=["Sine","Sawtooth","Square","Complex1", "Complex2", "Complex3", "Custom"]), cgen(name="custom", label="Custom Wave", init=[1,0,0.5,0.3,0,0,0.2,0,0.1,0,0.09,0,0.05], popup=("shape", 6), col="green2"), cpoly() ] cecilia5-5.4.1/Resources/modules/Synthesis/StochGrains.c5000066400000000000000000000461661372272363700233200ustar00rootroot00000000000000GRAIN_SAWTOOTH_WAVEFORM = SawTable(20) GRAIN_SQUARE_WAVEFORM = SquareTable(30) GRAIN_SINC_WAVEFORM = SincTable(freq=3.14159*16, windowed=True) GRAIN_PULSAR_WAVEFORM = SincTable(freq=3.14159*4, windowed=True) GRAIN_ENVELOPE_WAVEFORM = HannTable() GRAIN_SINE_WAVEFORM = HarmTable([1,0,0,.1,0,.3,0,0,.2,0,0,.1,0,0,.15,0,.1,0,0,.2,0,.1,0,0,.1,0,0,.07,0,.05]) class Grain: def __init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv): self.trig = Select(count, order) self.freq = SampHold(freq, self.trig, 1.0) self.dur = TrigXnoise(self.trig, mul=dur[1]-dur[0], add=dur[0]) self.det = TrigRand(self.trig, det[0], det[1]) self.bri = TrigRand(self.trig, bri[0], bri[1]) self.pan = TrigRand(self.trig, pan[0], pan[1]) self.ffr = TrigRand(self.trig, ffr[0], ffr[1]) self.fq = TrigRand(self.trig, fq[0], fq[1]) self.mul = TrigXnoise(self.trig, mul=mul[1]-mul[0], add=mul[0]) self.amp = TrigEnv(self.trig, env, self.dur, mul=self.mul*.25) def stop(self): for obj in self.__dict__.values(): obj.stop() return self def play(self): for obj in self.__dict__.values(): obj.play() return self class GrainFilter: def __init__(self, input, freq, q, type): self.filter = Biquad(input, freq, q, self.clipTypeToFilter(type)) self.output = Interp(input, self.filter, interp=self.clipTypeToInterp(type)) def stop(self): self.filter.stop() self.output.stop() def play(self): self.filter.play() self.output.play() def setType(self, x): self.filter.type = self.clipTypeToFilter(x) self.output.interp = self.clipTypeToInterp(x) def clipTypeToFilter(self, x): x = x - 1 if x < 0: return 0 return x def clipTypeToInterp(self, x): if x > 0: return 1 return 0 class GrainFM(Grain): def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls): Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv) self.ind = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*20) self.s1 = FM(carrier=self.freq, ratio=self.det*.4+1, index=self.ind, mul=self.amp) self.filter = GrainFilter(self.s1, freq=self.ffr, q=self.fq, type=ftype) self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan) class GrainSL(Grain): def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls): Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv) self.feed = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*.3) self.s1 = SineLoop(self.freq, feedback=self.feed, mul=self.amp) self.detune = self.det * .3 + 1 self.s2 = SineLoop(self.freq*self.detune, feedback=self.feed, mul=self.amp) self.filter = GrainFilter(self.s1+self.s2, freq=self.ffr, q=self.fq, type=ftype) self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan) class GrainBlit(Grain): def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls): Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv) self.cutoff = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*10000, add=100) self.s1 = Osc(GRAIN_SINC_WAVEFORM, self.freq, mul=self.amp) self.detune = self.det * .3 + 1 self.s2 = Osc(GRAIN_SINC_WAVEFORM, self.freq*self.detune, mul=self.amp) self.filt = Tone(self.s1+self.s2, self.cutoff, mul=4) self.filter = GrainFilter(self.filt, freq=self.ffr, q=self.fq, type=ftype) self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan) class GrainCrossFM(Grain): def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls): Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv) self.ind = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*4) self.s1 = CrossFM(carrier=self.freq, ratio=self.det*.4+1, ind1=4, ind2=self.ind, mul=self.amp) self.filter = GrainFilter(self.s1, freq=self.ffr, q=self.fq, type=ftype) self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan) class GrainSaw(Grain): def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls): Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv) self.cutoff = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*10000, add=100) self.s1 = Osc(GRAIN_SAWTOOTH_WAVEFORM, self.freq, mul=self.amp) self.detune = self.det * .3 + 1 self.s2 = Osc(GRAIN_SAWTOOTH_WAVEFORM, self.freq*self.detune, mul=self.amp) self.filt = Tone(self.s1+self.s2, self.cutoff, mul=.7) self.filter = GrainFilter(self.filt, freq=self.ffr, q=self.fq, type=ftype) self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan) class GrainSquare(Grain): def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls): Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv) self.cutoff = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*10000, add=100) self.s1 = Osc(GRAIN_SQUARE_WAVEFORM, self.freq, mul=self.amp) self.detune = self.det * .3 + 1 self.s2 = Osc(GRAIN_SQUARE_WAVEFORM, self.freq*self.detune, mul=self.amp) self.filt = Tone(self.s1+self.s2, self.cutoff, mul=.7) self.filter = GrainFilter(self.filt, freq=self.ffr, q=self.fq, type=ftype) self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan) class GrainPulsar(Grain): def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls): Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv) self.frac = TrigEnv(self.trig, brienv, self.dur, mul=self.bri) self.scl_frac = Scale(self.frac, 0, 1, 1, 0.05) self.s1 = Pulsar(GRAIN_PULSAR_WAVEFORM, env=GRAIN_ENVELOPE_WAVEFORM, freq=self.freq, frac=self.scl_frac, mul=self.amp) self.detune = self.det * .3 + 1 self.s2 = Pulsar(GRAIN_PULSAR_WAVEFORM, env=GRAIN_ENVELOPE_WAVEFORM, freq=self.freq*self.detune, frac=self.scl_frac, mul=self.amp) self.filter = GrainFilter(self.s1+self.s2, freq=self.ffr, q=self.fq, type=ftype) self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan) class GrainAddSynth(Grain): def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls): Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv) self.cutoff = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*10000, add=100) self.s1 = OscBank(GRAIN_SINE_WAVEFORM, freq=self.freq, spread=self.det*.25, slope=1, num=8, fjit=True, mul=self.amp) self.s2 = Tone(self.s1, self.cutoff, mul=4) self.filter = GrainFilter(self.s2, freq=self.ffr, q=self.fq, type=ftype) self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan) class Module(BaseModule): """ "Stochastic granular synthesis with different instrument tone qualities" Description This module implements a stochastic granular synthesis. Different synthesis engine are available and the user has control over the range of every generation parameters and envelopes. Sliders # Pitch Offset : Base transposition, in semitones, applied to every grain # Pitch Range : Range, in semitone, over which grain pitches are chosen randomly # Speed Range : Range, in second, over which grain start times are chosen randomly # Duration Range : Range, in second, over which grain durations are chosen randomly # Brightness Range : Range over which grain brightness factors (high frequency power) are chosen randomly # Detune Range : Range over which grain detune factors (frequency deviation between voices) are chosen randomly # Intensity Range : Range, in dB, over which grain amplitudes are chosen randomly # Pan Range : Range over which grain spatial positions are chosen randomly # Filter Freq Range : Range over which filter cutoff or center frequencies are chosen randomly # Filter Q Range : Range over which filter Qsare chosen # Density : Density of active grains, expressed as percent of the total generated grains # Global Seed : Root of stochatic generators. If 0, a new value is chosen randomly each time the performance starts. Otherwise, the same root is used every performance, making the generated sequences the same every time. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Grain Envelope : Amplitude envelope of each grain # Brightness Envelope : Brightness (high frequency power) envelope of each grain Popups & Toggles # Synth Type : Choose between the different synthesis engines # Pitch Scaling : Controls the possible values (as chords or scales) of the pitch generation # Pitch Algorithm : Noise distribution used by the pitch generator # Speed Algorithm : Noise distribution used by the speed generator # Duration Algorithm : Noise distribution used by the duration generator # Intensity Algorithm : Noise distribution used by the intensity generator # Filter Type : Type of the grain's filter (None, Lowpass, Highpass, Bandpass, Bandstop) # Max Num of Grains : Regardless the speed generation and the duration of each grain, there will never be more overlapped grains than this value. The more CPU power you have, higher this value can be. """ def __init__(self): BaseModule.__init__(self) self.setGlobalSeed(int(self.seed.get())) self.num = int(self.numofvoices_value) self.current_synth = self.synth_value self.scaledict = {'Major':[0,4,7], 'Minor':[0,3,7], 'Seventh':[0,4,7,10], 'Minor 7':[0,3,7,10], 'Major 7':[0,4,7,11], 'Minor 7 b5':[0,3,6,10], 'Diminished':[0,3,6], 'Diminished 7':[0,3,6,9], 'Minor 9':[0,3,7,10,14], 'Major 9':[0,4,7,11,14], 'Ninth':[0,4,7,10,14], 'Minor 11':[0,3,7,10,14,17], 'Major 11':[0,4,7,11,14,18], 'Eleventh':[0,4,7,10,14,18], 'Major 13':[0,4,7,11,14,18,21], 'Thirteenth':[0,4,7,10,14,18,21], 'Serial':[0,1,2,3,4,5,6,7,8,9,10,11], 'Whole-tone': [0,2,4,6,8,10]} self.stack_dict = {"FM": GrainFM, "Looped Sine": GrainSL, "Impulse train": GrainBlit, "AddSynth": GrainAddSynth, "CrossFM": GrainCrossFM, "Sawtooth": GrainSaw, "Square": GrainSquare, "Pulsar": GrainPulsar} self.speedgen = XnoiseDur(min=self.speed_rng[0], max=self.speed_rng[1]) self.new = Change(self.speedgen) self.newpass = Percent(self.new, self.density) self.count = VoiceManager(self.newpass) self.pitfloat = TrigXnoise(self.newpass, mul=self.pitch[1]-self.pitch[0], add=self.pitch_off+self.pitch[0]) self.freq = MToF(self.pitfloat) self.pitint = TrigXnoiseMidi(self.newpass, mul=0.007874015748031496) self.pitch_range = self.pitch[1]-self.pitch[0] self.scl = Snap(self.pitint*self.pitch_range+self.pitch[0]+self.pitch_off, choice=self.scaledict["Serial"], scale=1) self.frtostack = Sig(self.freq) self.mul_rng = DBToA(self.dbamp_rng) self.det_rng = Sig(self.detune_rng, mul=0.01) for key in self.stack_dict.keys(): stack = [self.stack_dict[key](i, self.count, self.frtostack, self.det_rng, self.bright_rng, self.dur_rng, self.pan_rng, self.ftype_index, self.ffreq_rng, self.fq_rng, self.mul_rng, self.grainenv, self.brightenv, self.nchnls).stop() for i in range(self.num)] stack_mix = Mix([gr.out for gr in stack], voices=self.nchnls).stop() self.stack_dict[key] = [stack, stack_mix] self.count.setTriggers([obj.amp["trig"] for obj in self.stack_dict[self.current_synth][0]]) [obj.play() for obj in self.stack_dict[self.current_synth][0]] self.stack_dict[self.current_synth][1].play() self.out = Sig(self.stack_dict[self.current_synth][1], mul=self.env) self.speedalgo(self.speedalgo_index, self.speedalgo_value) self.mulalgo(self.mulalgo_index, self.mulalgo_value) self.duralgo(self.duralgo_index, self.duralgo_value) self.pitalgo(self.pitalgo_index, self.pitalgo_value) self.genmethod(self.genmethod_index, self.genmethod_value) def synth(self, index, value): [obj.stop() for obj in self.stack_dict[self.current_synth][0]] [obj.play() for obj in self.stack_dict[value][0]] self.stack_dict[value][1].play() self.out.value = self.stack_dict[value][1] self.current_synth = value self.count.setTriggers([obj.amp["trig"] for obj in self.stack_dict[self.current_synth][0]]) def assignX1X2(self, index, *args): for arg in args: arg.dist = index if index in [4,5,6]: arg.x1 = 8 elif index == 7: arg.x1 = 2 elif index == 8: arg.x1 = 0.5 arg.x2 = 3.2 elif index == 9: arg.x1 = 0.5 arg.x2 = 1 elif index == 10: arg.x1 = 3 arg.x2 = 2 elif index in [11,12]: arg.x1 = 1 arg.x2 = .25 def speedalgo(self, index, value): self.assignX1X2(index, self.speedgen) def pitalgo(self, index, value): self.assignX1X2(index, self.pitfloat, self.pitint) def duralgo(self, index, value): for key in self.stack_dict.keys(): self.assignX1X2(index, *[obj.dur for obj in self.stack_dict[key][0]]) def mulalgo(self, index, value): for key in self.stack_dict.keys(): self.assignX1X2(index, *[obj.mul for obj in self.stack_dict[key][0]]) def genmethod(self, index, value): if value == "All-over": self.pitfloat.play() self.freq.play() self.frtostack.value = self.freq self.pitint.stop() self.scl.stop() else: self.scl.choice = self.scaledict[value] self.pitint.play() self.scl.play() self.frtostack.value = self.scl self.pitfloat.stop() self.freq.stop() def seed_up(self, value): self.setGlobalSeed(int(value)) def ftype(self, index, value): for key in self.stack_dict.keys(): [obj.filter.setType(index) for obj in self.stack_dict[key][0]] Interface = [ cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cgraph(name="grainenv", label="Grain Envelope", func=[(0,0),(.01,1),(.2,.4),(.5,.3),(1,0)], table=True, col="orange"), cgraph(name="brightenv", label="Brightness Envelope", func=[(0,1),(.2,.2),(1,0)], table=True, col="orange4"), cslider(name="pitch_off", label="Pitch Offset", min=-12, max=12, init=0, rel="lin", res="int", unit="midi", col="red"), crange(name="pitch", label="Pitch Range", min=12, max=115, init=[48,84], rel="lin", unit="midi", col="red4"), crange(name="speed_rng", label="Speed Range", min=.005, max=5, init=[.05, .25], rel="log", unit="sec", col="orange"), crange(name="dur_rng", label="Duration Range", min=.005, max=20, init=[.5,5], rel="log", unit="sec", col="orange2"), crange(name="bright_rng", label="Brightness Range", min=0, max=1, init=[0,.1], rel="lin", unit="x", col="blue2"), crange(name="detune_rng", label="Detune Range", min=0.01, max=100, init=[0.01, 0.05], rel="log", unit="x", col="blue3"), crange(name="dbamp_rng", label="Intensity Range", min=-90, max=0, init=[-18,-6], rel="lin", unit="dB", col="purple4"), crange(name="pan_rng", label="Pan Range", min=0, max=1, init=[0,1], rel="lin", unit="x", col="purple1"), crange(name="ffreq_rng", label="Filter Freq Range", min=50, max=18000, init=[500, 5000], rel="log", unit="Hz", col="green1"), crange(name="fq_rng", label="Filter Q Range", min=0.5, max=50, init=[1, 5], rel="log", unit="Q", col="green2"), cslider(name="density", label="Density", min=0, max=100, init=100, rel="lin", unit="%", col="orange"), cslider(name="seed", label="Global seed", min=0, max=5000, init=0, rel="lin", res="int", unit="x", up=True), cpopup(name="synth", label="Synth Type", value=['FM', 'Looped Sine', 'Impulse train', 'CrossFM', 'Sawtooth', 'Square', 'Pulsar', 'AddSynth'], init="FM", col="blue1"), cpopup(name="genmethod", label="Pitch Scaling", value=['All-over', 'Serial', 'Major', 'Minor', 'Seventh', 'Minor 7', 'Major 7', 'Minor 7 b5', 'Diminished', 'Diminished 7', 'Ninth', 'Major 9', 'Minor 9', 'Eleventh', 'Major 11', 'Minor 11', 'Thirteenth', 'Major 13', 'Whole-tone'], init="Major 11", col="red"), cpopup(name="pitalgo", label="Pitch Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], init="Uniform", col="red4"), cpopup(name="speedalgo", label="Speed Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], init="Uniform", col="orange"), cpopup(name="duralgo", label="Duration Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], init="Uniform", col="orange2"), cpopup(name="mulalgo", label="Intensity Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], init="Uniform", col="purple4"), cpopup(name="ftype", label="Filter Type", value=['None', 'Lowpass', 'Highpass', 'Bandpass', 'Bandstop'], init="None", col="green1"), cpopup(name="numofvoices", label="Max Num of Grains", value=['5','10','15','20','25','30','40','50','60'], init='10', rate="i") ] cecilia5-5.4.1/Resources/modules/Synthesis/StochGrains2.c5000066400000000000000000000277151372272363700234010ustar00rootroot00000000000000class GrainFilter: def __init__(self, input, freq, q, type): self.filter = Biquad(input, freq, q, self.clipTypeToFilter(type)) self.output = Interp(input, self.filter, interp=self.clipTypeToInterp(type)) def stop(self): self.filter.stop() self.output.stop() def play(self): self.filter.play() self.output.play() def setType(self, x): self.filter.type = self.clipTypeToFilter(x) self.output.interp = self.clipTypeToInterp(x) def clipTypeToFilter(self, x): x = x - 1 if x < 0: return 0 return x def clipTypeToInterp(self, x): if x > 0: return 1 return 0 class GrainSnd: def __init__(self, order, count, table, freq, start, dur, pan, ftype, ffr, fq, mul, env, nchnls, table_dur): self.trig = Select(count, order) self.freq = SampHold(freq, self.trig, 1.0) self.start_rng = SampHold(start, self.trig, 1.0) self.start = TrigXnoise(self.trig, mul=self.start_rng[1]-self.start_rng[0], add=self.start_rng[0]) self.dur_rng = SampHold(dur, self.trig, 1.0) self.dur = TrigXnoise(self.trig, mul=self.dur_rng[1]-self.dur_rng[0], add=self.dur_rng[0]) self.pan = TrigRand(self.trig, pan[0], pan[1]) self.ffr = TrigRand(self.trig, ffr[0], ffr[1]) self.fq = TrigRand(self.trig, fq[0], fq[1]) self.mul = TrigXnoise(self.trig, mul=mul[1]-mul[0], add=mul[0]) self.amp = TrigEnv(self.trig, env, self.dur, mul=self.mul*0.25) self.pointer = TrigEnv(self.trig, LinTable(), self.dur, mul=self.dur*self.freq/table_dur, add=self.start) self.s1 = Pointer(table, self.pointer, mul=self.amp) self.filter = GrainFilter(self.s1, freq=self.ffr, q=self.fq, type=ftype) self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan) class Module(BaseModule): """ "Stochastic granular synthesis based on a soundfile" Description This module implements a stochastic granular synthesis where grains coe from a given soundfile. The user has control over the range of every generation parameters and envelopes. Sliders # Pitch Offset : Base transposition, in semitones, applied to every grain # Pitch Range : Range, in semitone, over which grain transpositions are chosen randomly # Speed Range : Range, in second, over which grain start times are chosen randomly # Duration Range : Range, in second, over which grain durations are chosen randomly # Start Range : Range, in seconds, over which grain starting poistions (in the file) are chosen randomly # Intensity Range : Range, in dB, over which grain amplitudes are chosen randomly # Filter Freq Range : Range over which filter cutoff or center frequencies are chosen randomly # Filter Q Range : Range over which filter Qsare chosen # Pan Range : Range over which grain spatial positions are chosen randomly # Density : Density of active grains, expressed as percent of the total generated grains # Global Seed : Root of stochatic generators. If 0, a new value is chosen randomly each time the performance starts. Otherwise, the same root is used every performance, making the generated sequences the same every time. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Grain Envelope : Amplitude envelope of each grain Popups & Toggles # Pitch Scaling : Controls the possible values (as chords or scales) of the pitch generation # Pitch Algorithm : Noise distribution used by the pitch generator # Speed Algorithm : Noise distribution used by the speed generator # Duration Algorithm : Noise distribution used by the duration generator # Intensity Algorithm : Noise distribution used by the intensity generator # Filter Type : Type of the grain's filter (None, Lowpass, Highpass, Bandpass, Bandstop) # Max Num of Grains : Regardless the speed generation and the duration of each grain, there will never be more overlapped grains than this value. The more CPU power you have, higher this value can be. """ def __init__(self): BaseModule.__init__(self) self.table = self.addFilein("snd") self.table_dur = self.table.getDur() self.setGlobalSeed(int(self.seed.get())) self.num = int(self.numofvoices_value) self.scaledict = {'Major':[0,4,7], 'Minor':[0,3,7], 'Seventh':[0,4,7,10], 'Minor 7':[0,3,7,10], 'Major 7':[0,4,7,11], 'Minor 7 b5':[0,3,6,10], 'Diminished':[0,3,6], 'Diminished 7':[0,3,6,9], 'Minor 9':[0,3,7,10,14], 'Major 9':[0,4,7,11,14], 'Ninth':[0,4,7,10,14], 'Minor 11':[0,3,7,10,14,17], 'Major 11':[0,4,7,11,14,18], 'Eleventh':[0,4,7,10,14,18], 'Major 13':[0,4,7,11,14,18,21], 'Thirteenth':[0,4,7,10,14,18,21], 'Serial':[0,1,2,3,4,5,6,7,8,9,10,11], 'Whole-tone': [0,2,4,6,8,10]} self.speedgen = XnoiseDur(min=self.speed_rng[0], max=self.speed_rng[1]) self.new = Change(self.speedgen) self.newpass = Percent(self.new, self.density) self.count = VoiceManager(self.newpass) self.pitfloat = TrigXnoise(self.newpass, mul=self.pitch[1]-self.pitch[0], add=self.pitch_off+self.pitch[0]) self.freq = MToT(self.pitfloat) self.pitint = TrigXnoiseMidi(self.newpass, mrange=(0, 120), mul=0.007874015748031496) self.pitch_range = self.pitch[1]-self.pitch[0] self.scl = Snap(self.pitint*self.pitch_range+self.pitch[0]+self.pitch_off, choice=self.scaledict["Serial"], scale=2) self.frtostack = Sig(self.freq) self.mul_rng = DBToA(self.dbamp_rng) self.stack = [GrainSnd(i, self.count, self.table, self.frtostack, self.start_rng, self.dur_rng, self.pan_rng, self.ftype_index, self.ffreq_rng, self.fq_rng, self.mul_rng, self.grainenv, self.nchnls, self.table_dur) for i in range(self.num)] self.stack_mix = Mix([gr.out for gr in self.stack], voices=self.nchnls) self.out = Sig(self.stack_mix, mul=self.env) self.count.setTriggers([obj.amp["trig"] for obj in self.stack]) self.speedalgo(self.speedalgo_index, self.speedalgo_value) self.mulalgo(self.mulalgo_index, self.mulalgo_value) self.duralgo(self.duralgo_index, self.duralgo_value) self.pitalgo(self.pitalgo_index, self.pitalgo_value) self.genmethod(self.genmethod_index, self.genmethod_value) def assignX1X2(self, index, *args): for arg in args: arg.dist = index if index in [4,5,6]: arg.x1 = 8 elif index == 7: arg.x1 = 2 elif index == 8: arg.x1 = 0.5 arg.x2 = 3.2 elif index == 9: arg.x1 = 0.5 arg.x2 = 1 elif index == 10: arg.x1 = 3 arg.x2 = 2 elif index in [11,12]: arg.x1 = 1 arg.x2 = .25 def speedalgo(self, index, value): self.assignX1X2(index, self.speedgen) def pitalgo(self, index, value): self.assignX1X2(index, self.pitfloat, self.pitint) def duralgo(self, index, value): self.assignX1X2(index, *[obj.dur for obj in self.stack]) def mulalgo(self, index, value): self.assignX1X2(index, *[obj.mul for obj in self.stack]) def genmethod(self, index, value): if value == "All-over": self.pitfloat.play() self.freq.play() self.frtostack.value = self.freq self.pitint.stop() self.scl.stop() else: self.scl.choice = self.scaledict[value] self.pitint.play() self.scl.play() self.frtostack.value = self.scl self.pitfloat.stop() self.freq.stop() def seed_up(self, value): self.setGlobalSeed(int(value)) def ftype(self, index, value): [obj.filter.setType(index) for obj in self.stack] Interface = [ cfilein(name="snd", label="Audio"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cgraph(name="grainenv", label="Grain Envelope", func=[(0,0),(.1,1),(.4,.8),(.7,.3),(1,0)], table=True, col="orange1"), cslider(name="pitch_off", label="Pitch Offset", min=-12, max=12, init=0, rel="lin", res="int", unit="midi", col="red1"), crange(name="pitch", label="Pitch Range", min=12, max=115, init=[48,72], rel="lin", unit="midi", col="red2"), crange(name="speed_rng", label="Speed Range", min=0.0005, max=5, init=[.05, .25], rel="log", unit="sec", col="orange1"), crange(name="dur_rng", label="Duration Range", min=0.001, max=10, init=[.25,2], rel="log", unit="sec", col="orange2"), crange(name="start_rng", label="Start Range", min=0, max=1, init=[.1,.5], rel="lin", unit="%", col="purple1"), crange(name="dbamp_rng", label="Intensity Range", min=-90, max=0, init=[-18,-6], rel="lin", unit="dB", col="blue2"), crange(name="pan_rng", label="Pan Range", min=0, max=1, init=[0,1], rel="lin", unit="x", col="orange1"), cslider(name="density", label="Density", min=0, max=100, init=100, rel="lin", unit="%", col="orange3"), crange(name="ffreq_rng", label="Filter Freq Range", min=50, max=18000, init=[500, 5000], rel="log", unit="Hz", col="green1"), crange(name="fq_rng", label="Filter Q Range", min=0.5, max=50, init=[1, 5], rel="log", unit="Q", col="green2"), cslider(name="seed", label="Global seed", min=0, max=5000, init=0, rel="lin", res="int", unit="x", up=True), cpopup(name="genmethod", label="Pitch Scaling", value=['All-over', 'Serial', 'Major', 'Minor', 'Seventh', 'Minor 7', 'Major 7', 'Minor 7 b5', 'Diminished', 'Diminished 7', 'Ninth', 'Major 9', 'Minor 9', 'Eleventh', 'Major 11', 'Minor 11', 'Thirteenth', 'Major 13', 'Whole-tone'], init="Major 11", col="red1"), cpopup(name="pitalgo", label="Pitch Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], init="Uniform", col="red2"), cpopup(name="speedalgo", label="Speed Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], init="Uniform", col="orange1"), cpopup(name="duralgo", label="Duration Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], init="Uniform", col="orange2"), cpopup(name="mulalgo", label="Intensity Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], init="Uniform", col="blue2"), cpopup(name="ftype", label="Filter Type", value=['None', 'Lowpass', 'Highpass', 'Bandpass', 'Bandstop'], init="None", col="green1"), cpopup(name="numofvoices", label="Max Num of Grains", value=['5','10','15','20','25','30','40','50','60'], init='10', rate="i") ] cecilia5-5.4.1/Resources/modules/Synthesis/WaveScanSynth.c5000066400000000000000000000066271372272363700236270ustar00rootroot00000000000000class Module(BaseModule): """ "Synthesis where the waveform is a small portion extracted from an audio signal" Description This module uses a small portion of an audio signal (between 64 and 8192 samples) as the waveform for an oscillator. By sliding the position of the portion in the signal, one can produce continuous complex variations of the waveform, which can give very rich sonorities. Sliders # Base Frequency : Fundamental frequency of the oscillator. # Position : Position in the signal where to extract the waveform. # Window Size : Lentgh, in samples, of the waveform. # LFO Frequency : Frequency of an LFO applied to the position in the signal. # LFO Depth : Depth of the LFO applied to the position in the signal. # Lowpass Freq : Frequency of a lowpass filter used to attenuate the higher components in the spectrum of the resulting signal. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.table = self.addFilein("table") self.phase = Phasor(freq=self.frequency * self.polyphony_spread) self.triangle = Min(self.phase, comp=self.phase* -1 + 1, mul=2) self.winsizePort = Port(self.winsize / self.polyphony_spread, risetime=0.5, falltime=0.5, init=self.winsize.get(), mul=1. / self.table.getSize(False)) self.window = self.triangle * self.winsizePort self.lfodepthPort = Port(self.lfodepth, risetime=0.5, falltime=0.5, init=self.lfodepth.get(), mul=0.1) self.lfo = Sine(self.lfofreq, mul=self.lfodepthPort) self.positionPort = Port(self.position, risetime=0.25, falltime=0.25, init=self.position.get()) self.pointerPos = self.positionPort + self.window + self.lfo self.reader = Pointer2(self.table, index=self.pointerPos, mul=0.7) self.readerBL = IRWinSinc(self.reader, freq=0, order=16) self.filter = Biquadx(self.readerBL, freq=self.lowfreq, q=0.7, stages=2) self.out = Mix(self.filter, voices=self.nchnls, mul=self.env * self.polyphony_scaling) Interface = [ cfilein(name="table"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="frequency", label="Base Frequency", min=1, max=1000, init=100, rel="log", unit="Hz", col="red"), cslider(name="position", label="Position", min=0, max=1, init=0.5, rel="lin", unit="x", col="orange"), cslider(name="winsize", label="Window Size", min=64, max=8192, init=512, rel="lin", res="int", unit="spl", col="orange2"), cslider(name="lfofreq", label="LFO Frequency", min=0.001, max=100, init=0.1, rel="log", unit="Hz", col="purple"), cslider(name="lfodepth", label="LFO Depth", min=0, max=1, init=0.1, rel="lin", unit="x", col="purple2"), cslider(name="lowfreq", label="Lowpass Freq", min=50, max=15000, init=2500, rel="log", unit="Hz", col="green"), cpoly() ] cecilia5-5.4.1/Resources/modules/Time/000077500000000000000000000000001372272363700175335ustar00rootroot00000000000000cecilia5-5.4.1/Resources/modules/Time/4Delays.c5000066400000000000000000000154121372272363700212740ustar00rootroot00000000000000class Module(BaseModule): """ "Two stereo delays with parallel or serial routing" Description A classical module implementing a pair of stereo delays with user-defined delay time, feedback and gain. Routing of delays and filters can be defined with the popup menus. Sliders # Delay 1 Left : Delay one, left channel, delay time in seconds # Delay 1 Right : Delay one, right channel, delay time in seconds # Delay 1 Feedback : Amount of delayed signal fed back to the delay line input # Delay 1 Gain : Gain of the delayed signal # Delay 2 Left : Delay two, left channel, delay time in seconds # Delay 2 Right : Delay two, right channel, delay time in seconds # Delay 2 Feedback : Amount of delayed signal fed back to the delay line input # Delay 2 Gain : Gain of the delayed signal # Jitter Amp : Amplitude of the jitter applied on the delay times # Jitter Speed : Speed of the jitter applied on the delay times # Filter Freq : Cutoff or center frequency of the filter # Filter Q : Q factor of the filter # Dry / Wet : Mix between the original signal and the processed signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Delay Routing : Type of routing # Filter Type : Type of filter # Filter Routing : Specify if the filter is pre or post process # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.prefilt = Biquadx(self.snd, freq=self.filter, q=self.filterq, type=self.filttype_index, stages=2, mul=0.25) self.filtChoice = SigTo(0, time=0.005, init=0) self.toDelays = Interp(self.prefilt, self.snd, interp=self.filtChoice) self.jit = Randi(min=1-self.jitamp, max=1+self.jitamp, freq=self.jitspeed, mul=[1]*4) self.delay1 = Delay(self.toDelays, delay=[self.del1l*self.jit[0],self.del1r*self.jit[1]], feedback=self.del1f, maxdelay=10, mul=DBToA(self.del1m)) self.delay2 = Delay(self.toDelays, delay=[self.del2l*self.jit[2],self.del2r*self.jit[3]], feedback=self.del2f, maxdelay=10, mul=DBToA(self.del2m)) self.dels = self.delay1+self.delay2 self.postfilt = Biquadx(self.dels, freq=self.filter, q=self.filterq, type=self.filttype_index, stages=2, mul=0.25) self.toOuts = Interp(self.dels, self.postfilt, interp=self.filtChoice) self.deg = Interp(self.snd*0.25, self.toOuts, self.drywet, mul=self.env) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.deg, self.osc, freq=10) self.out = Interp(self.deg, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) self.routing(self.routing_index, self.routing_value) self.filtrouting(self.filtrouting_index, self.filtrouting_value) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.snd def filttype(self, index, value): self.prefilt.type = index self.postfilt.type = index def routing(self, index, value): if index == 0: self.delay2.setInput(self.delay1, 0.1) self.postfilt.setInput(self.delay2, 0.1) else: self.delay2.setInput(self.toDelays, 0.1) self.postfilt.setInput(self.dels, 0.1) def filtrouting(self, index, value): self.filtChoice.value = index Interface = [ csampler(name="snd", label="Audio"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="del1l", label="Delay 1 Left", min=0.0001, max=10, init=0.26, gliss=0.1, rel="log", unit="sec", half=True, col="blue1"), cslider(name="del2l", label="Delay 2 Left", min=0.0001, max=10, init=0.16, gliss=0.1, rel="log", unit="sec", half=True, col="green1"), cslider(name="del1r", label="Delay 1 Right", min=0.0001, max=10, gliss=0.1, init=0.25, rel="log", unit="sec", half=True, col="blue2"), cslider(name="del2r", label="Delay 2 Right", min=0.0001, max=10, init=0.15, gliss=0.1, rel="log", unit="sec", half=True, col="green2"), cslider(name="del1f", label="Delay 1 Feedback", min=0, max=0.999, init=0.5, rel="lin", unit="x", half=True, col="blue3"), cslider(name="del2f", label="Delay 2 Feedback", min=0, max=0.999, init=0.5, rel="lin", unit="x", half=True, col="green3"), cslider(name="del1m", label="Delay 1 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", half=True, col="blue4"), cslider(name="del2m", label="Delay 2 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", half=True, col="green4"), cslider(name="jitamp", label="Jitter Amp", min=0.0001, max=1, init=0.1, rel="log", unit="x", col="red1", half=True), cslider(name="jitspeed", label="Jitter Speed", min=0.0001, max=50, init=0.03, rel="log", unit="Hz", col="red2", half=True), cslider(name="filter", label="Filter Freq", min=30, max=20000, init=15000, rel="log", unit="Hz", col="orange1"), cslider(name="filterq", label="Filter Q", min=0.5, max=10, init=0.707, rel="log", unit="Q", col="orange2"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpopup(name="routing", label="Delay Routing", init="Parallel", col="purple1", value=["Serial","Parallel"]), cpopup(name="filttype", label="Filter Type", init="Lowpass", col="orange1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="filtrouting", label="Filter Routing", init="Pre", col="orange1", value=["Pre","Post"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Time/BeatMaker.c5000066400000000000000000000260211372272363700216200ustar00rootroot00000000000000class Module(BaseModule): """ "Algorithmic beat maker module" Description Four voices beat generator where sounds are extracted from a soundfile. Sliders # Num of Taps : Number of taps in a measure # Tempo : Speed of taps in BPM (ref. to quarter note) # Beat 1 Tap Length : Length of taps of the first beat, in seconds # Beat 1 Index : Soundfile index of the first beat # Beat 1 Gain : Gain of the first beat # Beat 1 Distribution : Repartition of taps for the first beat (100% weak <--> 100% down) # Beat 2 Tap Length : Length of taps of the second beat, in seconds # Beat 2 Index : Soundfile index of the second beat # Beat 2 Gain : Gain of the second beat # Beat 2 Distribution : Repartition of taps for the second beat (100% weak <--> 100% down) # Beat 3 Tap Length : Length of taps of the third beat, in seconds # Beat 3 Index : Soundfile index of the third beat # Beat 3 Gain : Gain of the third beat # Beat 3 Distribution : Repartition of taps for the third beat (100% weak <--> 100% down) # Beat 4 Tap Length : Length of taps of the fourth beat, in seconds # Beat 4 Index : Soundfile index of the fourth beat # Beat 4 Gain : Gain of the fourth beat # Beat 4 Distribution : Repartition of taps for the fourth beat (100% weak <--> 100% down) # Global Seed : Seed value for the algorithmic beats, using the same seed with the same distributions will yield the exact same beats. Graph Only # Beat 1 ADSR : Envelope of taps for the first beat in breakpoint fashion # Beat 2 ADSR : Envelope of taps for the second beat in breakpoint fashion # Beat 3 ADSR : Envelope of taps for the third beat in breakpoint fashion # Beat 4 ADSR : Envelope of taps for the fourth beat in breakpoint fashion # Overall Amplitude : The amplitude curve applied on the total duration of the performance """ def __init__(self): BaseModule.__init__(self) self.snd = self.addFilein("snd") self.last_we1 =self.last_we2 = self.last_we3 = self.last_we4 = self.last_taps = -1 self.rtempo = 1/(self.tempo/15) self.setGlobalSeed(int(self.seed.get())) w1, w2, w3 = self.newdist1(int(self.we1.get())) self.beat1 = Beat(time=self.rtempo, taps=int(self.taps.get()), w1=w1, w2=w2, w3=w3, poly=10).play() w1, w2, w3 = self.newdist2(int(self.we2.get())) self.beat2 = Beat(time=self.rtempo, taps=int(self.taps.get()), w1=w1, w2=w2, w3=w3, poly=10).play() w1, w2, w3 = self.newdist3(int(self.we3.get())) self.beat3 = Beat(time=self.rtempo, taps=int(self.taps.get()), w1=w1, w2=w2, w3=w3, poly=10).play() w1, w2, w3 = self.newdist4(int(self.we4.get())) self.beat4 = Beat(time=self.rtempo, taps=int(self.taps.get()), w1=w1, w2=w2, w3=w3, poly=10).play() self.tre1 = TrigEnv(input=self.beat1, table=self.adsr1, dur=self.tapsl1, mul=self.beat1['amp']) self.tre2 = TrigEnv(input=self.beat2, table=self.adsr2, dur=self.tapsl2, mul=self.beat2['amp']) self.tre3 = TrigEnv(input=self.beat3, table=self.adsr3, dur=self.tapsl3, mul=self.beat3['amp']) self.tre4 = TrigEnv(input=self.beat4, table=self.adsr4, dur=self.tapsl4, mul=self.beat4['amp']) self.linseg1 = TrigLinseg(input=self.beat1, list=[(0,self.bindex1.get()),(self.tapsl1.get(),1/(self.snd.getDur(False)/self.tapsl1.get())+self.bindex1.get())]) self.linseg2 = TrigLinseg(input=self.beat2, list=[(0,self.bindex2.get()),(self.tapsl2.get(),(1/(self.snd.getDur(False)/self.tapsl2.get()))+self.bindex2.get())]) self.linseg3 = TrigLinseg(input=self.beat3, list=[(0,self.bindex3.get()),(self.tapsl3.get(),(1/(self.snd.getDur(False)/self.tapsl3.get()))+self.bindex3.get())]) self.linseg4 = TrigLinseg(input=self.beat4, list=[(0,self.bindex4.get()),(self.tapsl4.get(),(1/(self.snd.getDur(False)/self.tapsl4.get()))+self.bindex4.get())]) self.trf1 = TrigFunc(self.linseg1['trig'], self.newseg1) self.trf2 = TrigFunc(self.linseg2['trig'], self.newseg2) self.trf3 = TrigFunc(self.linseg3['trig'], self.newseg3) self.trf4 = TrigFunc(self.linseg4['trig'], self.newseg4) self.again1 = DBToA(self.gain1, mul=0.7) self.again2 = DBToA(self.gain2, mul=0.7) self.again3 = DBToA(self.gain3, mul=0.7) self.again4 = DBToA(self.gain4, mul=0.7) self.pointer1 = Pointer(table=self.snd, index=self.linseg1, mul=self.tre1*self.again1) self.pointer2 = Pointer(table=self.snd, index=self.linseg2, mul=self.tre2*self.again2) self.pointer3 = Pointer(table=self.snd, index=self.linseg3, mul=self.tre3*self.again3) self.pointer4 = Pointer(table=self.snd, index=self.linseg4, mul=self.tre4*self.again4) self.mixx = self.pointer1+self.pointer2+self.pointer3+self.pointer4 self.out = Sig(self.mixx, mul=self.env).mix(self.nchnls) self.trigend = TrigFunc(self.beat1["end"], self.newdist) def seed_up(self, value): self.setGlobalSeed(int(value)) self.beat1.new() self.beat2.new() self.beat3.new() self.beat4.new() def newseg1(self): self.linseg1.setList([(0,self.bindex1.get()),(self.tapsl1.get(),1/(self.snd.getDur(False)/self.tapsl1.get())+self.bindex1.get())]) def newseg2(self): self.linseg2.setList([(0,self.bindex2.get()),(self.tapsl2.get(),1/(self.snd.getDur(False)/self.tapsl2.get())+self.bindex2.get())]) def newseg3(self): self.linseg3.setList([(0,self.bindex3.get()),(self.tapsl3.get(),1/(self.snd.getDur(False)/self.tapsl3.get())+self.bindex3.get())]) def newseg4(self): self.linseg4.setList([(0,self.bindex4.get()),(self.tapsl4.get(),1/(self.snd.getDur(False)/self.tapsl4.get())+self.bindex4.get())]) def newtaps(self, value): self.beat1.setTaps(value) self.beat2.setTaps(value) self.beat3.setTaps(value) self.beat4.setTaps(value) def newdist1(self, value): w1 = value if value <= 50: w2 = value*2 else: w2 = (100-value)*2 w3 = 100-value return (w1, w2, w3) def newdist2(self, value): w1 = value if value <= 50: w2 = value*2 else: w2 = (100-value)*2 w3 = 100-value return (w1, w2, w3) def newdist3(self, value): w1 = value if value <= 50: w2 = value*2 else: w2 = (100-value)*2 w3 = 100-value return (w1, w2, w3) def newdist4(self, value): w1 = value if value <= 50: w2 = value*2 else: w2 = (100-value)*2 w3 = 100-value return (w1, w2, w3) def newdist(self): taps = int(self.taps.get()) if taps != self.last_taps: self.last_taps = taps self.newtaps(taps) value = int(self.we1.get()) if value != self.last_we1: self.last_we1 = value self.beat1.setWeights(*self.newdist1(value)) value = int(self.we2.get()) if value != self.last_we2: self.last_we2 = value self.beat2.setWeights(*self.newdist2(value)) value = int(self.we3.get()) if value != self.last_we3: self.last_we3 = value self.beat3.setWeights(*self.newdist3(value)) value = int(self.we4.get()) if value != self.last_we4: self.last_we4 = value self.beat4.setWeights(*self.newdist4(value)) Interface = [ cfilein(name="snd", label="Audio"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cgraph(name="adsr1", label="Beat 1 ADSR", func=[(0,0),(0.001,1),(0.125,0.25),(1,0)], table=True, col="blue1"), cgraph(name="adsr2", label="Beat 2 ADSR", func=[(0,0),(0.005,1),(0.135,0.3),(1,0)], table=True, col="purple1"), cgraph(name="adsr3", label="Beat 3 ADSR", func=[(0,0),(0.01,1),(0.145,0.35),(1,0)], table=True, col="red1"), cgraph(name="adsr4", label="Beat 4 ADSR", func=[(0,0),(0.015,1),(0.155,0.4),(1,0)], table=True, col="green1"), cslider(name="taps", label="Num of Taps", min=1, max=64, init=16, res="int", rel="lin", unit="x", col= "orange1"), cslider(name="tempo", label="Tempo", min=20, max=240, gliss=0, init=120, rel="lin", unit="bpm", col="orange3"), cslider(name="tapsl1", label="Beat 1 Tap Length", min=0.01, max=1, init=0.2, rel="lin", unit="sec", col="blue1",half=True), cslider(name="tapsl3", label="Beat 3 Tap Length", min=0.01, max=1, init=0.2, rel="lin", unit="sec", col="red1",half=True), cslider(name="bindex1", label="Beat 1 Index", min=0, max=1, init=0, rel="lin", unit="x", col="blue2",half=True), cslider(name="bindex3", label="Beat 3 Index", min=0, max=1, init=0.25, rel="lin", unit="x", col="red2",half=True), cslider(name="gain1", label="Beat 1 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="blue3",half=True), cslider(name="gain3", label="Beat 3 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="red3",half=True), cslider(name="we1", label="Beat 1 Distribution", min=0, max=100, init=80, res="int", rel="lin", unit="%", col="blue4",half=True), cslider(name="we3", label="Beat 3 Distribution", min=0, max=100, init=30, rel="lin", res="int", unit="%", col="red4",half=True), cslider(name="tapsl2", label="Beat 2 Tap Length", min=0.01, max=1, init=0.2, rel="lin", unit="sec", col="purple1",half=True), cslider(name="tapsl4", label="Beat 4 Tap Length", min=0.01, max=1, init=0.2, rel="lin", unit="sec", col="green1",half=True), cslider(name="bindex2", label="Beat 2 Index", min=0, max=1, init=0.5, rel="lin", unit="x", col="purple2",half=True), cslider(name="bindex4", label="Beat 4 Index", min=0, max=1, init=0.75, rel="lin", unit="x", col="green2",half=True), cslider(name="gain2", label="Beat 2 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="purple3",half=True), cslider(name="gain4", label="Beat 4 Gain", min=-48, max=18, init=0, rel="lin", unit="dB", col="green3",half=True), cslider(name="we2", label="Beat 2 Distribution", min=0, max=100, init=50, rel="lin", res="int", unit="%", col="purple4",half=True), cslider(name="we4", label="Beat 4 Distribution", min=0, max=100, init=10, rel="lin", res="int", unit="%", col="green4",half=True), cslider(name="seed", label="Global seed", min=0, max=5000, init=0, rel="lin", res="int", unit="x", up=True), ] cecilia5-5.4.1/Resources/modules/Time/DelayMod.c5000066400000000000000000000110361372272363700214630ustar00rootroot00000000000000class Module(BaseModule): """ "Stereo delay module with LFO on delay times" Description A stereo delay whose delay times are modulated with LFO of different shapes. Sliders # Delay Time L : Delay time of the left channel delay # Delay Time R : Delay time of the right channel delay # LFO Depth L : Amplitude of the LFO applied on left channel delay time # LFO Depth R : Amplitude of the LFO applied on right channel delay time # LFO Freq L : Frequency of the LFO applied on left channel delay time # LFO Freq R : Frequency of the LFO applied on right channel delay time # Gain Delay L : Amplitude of the left channel delay # Gain Delay R : Amplitude of the right channel delay # Feedback : Amount of delayed signal fed back in the delay chain # LFO Sharpness : Sharper waveform results in more harmonics in the LFO spectrum. # Dry / Wet : Mix between the original signal and the delayed signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # LFO Waveform L : Shape of the LFO waveform applied on left channel delay # LFO Waveform R : Shape of the LFO waveform applied on right channel delay # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.lfol = LFO(self.speedl, sharp=self.sharp, mul=self.depthl, type=self.wavel_index, add=1) self.lfor = LFO(self.speedr, sharp=self.sharp, mul=self.depthr, type=self.waver_index, add=1) self.ampl = DBToA(self.gainl) self.ampr = DBToA(self.gainr) self.delay = Delay(self.snd, delay=[self.dell*self.lfol,self.delr*self.lfor], feedback=self.fb, maxdelay=20, mul=[self.ampl, self.ampr]) self.out = Interp(self.snd, self.delay, self.drywet, mul=self.env*0.5) def wavel(self, index, value): self.lfol.type = (index - 1) % 8 def waver(self, index, value): self.lfor.type = (index - 1) % 8 Interface = [ csampler(name="snd", label="Audio"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="dell", label="Delay Time L", min=0.0001, max=10, init=0.15, gliss=0.1, rel="log", unit="sec", half=True, col="purple1"), cslider(name="delr", label="Delay Time R", min=0.0001, max=10, init=0.25, gliss=0.1, rel="log", unit="sec", half=True, col="red1"), cslider(name="depthl", label="LFO Depth L", min=0.001, max=0.5, init=0.05, rel="log", unit="x", half=True, col="purple2"), cslider(name="depthr", label="LFO Depth R", min=0.001, max=0.5, init=0.05, rel="log", unit="x", half=True, col="red2"), cslider(name="speedl", label="LFO Freq L", min=0.001, max=200, init=1, rel="log", unit="Hz", half=True, col="purple3"), cslider(name="speedr", label="LFO Freq R", min=0.001, max=200, init=1.1, rel="log", unit="Hz", half=True, col="red3"), cslider(name="gainl", label="Gain Delay L", min=-48, max=18, init=0, rel="lin", unit="dB", half=True, col="purple4"), cslider(name="gainr", label="Gain Delay R", min=-48, max=18, init=0, rel="lin", unit="dB", half=True, col="red4"), cslider(name="fb", label="Feedback", min=0, max=0.999, init=0.5, rel="lin", unit="x", col="green1"), cslider(name="sharp", label="LFO Sharpness", min=0, max=1, init=0.5, rel="lin", unit="x", col="green2"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=0.7, rel="lin", unit="x", col="blue1"), cpopup(name="wavel", label="LFO Waveform L", init="Sine", col="purple2", value=["Sine", "Saw Up", "Saw Down", "Square", "Triangle", "Pulse", "Bipolar Pulse", "SAH"]), cpopup(name="waver", label="LFO Waveform R", init="Sine", col="red2", value=["Sine", "Saw Up", "Saw Down", "Square", "Triangle", "Pulse", "Bipolar Pulse", "SAH"]), cpoly() ] cecilia5-5.4.1/Resources/modules/Time/Granulator.c5000066400000000000000000000120071372272363700221020ustar00rootroot00000000000000import random class Module(BaseModule): """ "Granulation module" Description A classic granulation module. Useful to stretch a sound without changing the pitch or to transpose without changing the duration. Sliders # Transpose : Base pitch of the grains # Grain Position : Soundfile index # Position Random : Jitter applied on the soundfile index # Pitch Random : Jitter applied on the pitch of the grains using the discreet transpo list # Grain Duration : Length of the grains # Num of Grains : Number of overlapping grains Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Grain Envelope : Emplitude envelope of the grains Popups & Toggles # Filter Type : Type of the post filter # Discreet Transpo : List of pitch ratios # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.amp_scl = Map(350, 1, "lin") firstamp = self.amp_scl.set(self.num.get()) pitrnds = self.polyphony_spread self.table = self.addFilein("sndtable") self.posr = Noise(self.posrnd*self.table.getSize(False)) self.pitr = Noise(self.pitrnd, add=1) self.discr = Choice(choice=self.discreet_value, freq=1000) self.pitch = CentsToTranspo(self.transp, mul=pitrnds) self.pospos = Sig(self.pos, mul=self.table.getSize(False), add=self.posr) self.gr = Granulator(self.table, self.grainenv, pitch=self.pitch, pos=self.pospos, dur=self.dur.get()*self.pitr*self.discr, grains=int(self.num.get()), basedur=self.dur.get(), mul=self.env* 0.1 * firstamp) self.gro = Biquadx(self.gr.mix(self.nchnls), freq=self.cut, q=self.filterq, type=self.filttype_index, stages=2, mul=self.polyphony_scaling) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.gro, self.osc, freq=10) self.out = Interp(self.gro, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self, index, value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.gr def filttype(self, index, value): self.gro.type = index def dur_up(self, value): self.gr.basedur = value self.gr.dur = value * self.pitr * self.discr def num_up(self, value): gr = int(value) self.gr.grains = gr self.gr.mul = self.env * 0.1 * self.amp_scl.set(gr) def discreet(self, value): self.discr.choice = value Interface = [ cfilein(name="sndtable", label="Audio"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cgraph(name="grainenv", label="Grain Envelope", func=[(0,0),(0.5,1),(1,0)], table=True, curved=True, col="purple1"), cslider(name="transp", label="Transpose", min=-4800, max=4800, init=0, rel="lin", unit="cnts", col="red1"), cslider(name="pos", label="Grain Position", min=0, max=1, init=0, func=[(0,0),(1,1)], rel="lin", unit="x", col="purple1"), cslider(name="posrnd", label="Position Random", min=0.00001, max=0.5, init=0.001, rel="log", unit="x", col="orange2", half=True), cslider(name="pitrnd", label="Pitch Random", min=0.00001, max=0.5, init=0.001, rel="log", unit="x", col="orange3", half=True), cslider(name="cut", label="Filter Freq", min=100, max=18000, init=20000, rel="log", unit="Hz", col="green1"), cslider(name="filterq", label="Filter Q", min=0.5, max=10, init=0.707, rel="log", unit="Q", col="green2"), cslider(name="dur", label="Grain Duration", min=0.01, max=10, init=0.1, up=True, rel="log", unit="sec", half=True), cslider(name="num", label="Num of Grains", min=1, max=256, init=32, rel="lin", gliss=0, res="int", up=True, unit="grs", half=True), cpopup(name="filttype", label="Filter Type", init="Lowpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cgen(name="discreet", label="Discreet Transpo", init=[1], col="red1"), cpoly() ] cecilia5-5.4.1/Resources/modules/Time/Particle.c5000066400000000000000000000130211372272363700215240ustar00rootroot00000000000000import random class Module(BaseModule): """ "A full-featured granulation module" Description This module offers more controls than the classic granulation module. Useful to generate clouds, to stretch a sound without changing the pitch or to transpose without changing the duration. `Density per Second * Grain Duration` defines the overall overlaps. Sliders # Density per Sec : How many grains to play per second # Transpose : Overall transposition, in cents, of the grains # Grain Position : Soundfile index # Grain Duration : Duration of each grain, in seconds # Start Time Dev : Maximum deviation of the starting time of the grain # Panning Random : Random added to the panning of each grain # Density Random : Jitter applied to the density per second # Pitch Random : Jitter applied on the pitch of the grains # Position Random : Jitter applied on the soundfile index # Duration Random : Jitter applied to the grain duration Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Grain Envelope : Emplitude envelope of the grains Popups & Toggles # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Discreet Transpo : List of pitch ratios used to randomize grain's transposition # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.table = self.addFilein("sndtable") self.densr = Randi(-self.dnsrnd, self.dnsrnd, freq=1, add=1) self.posr = Noise(self.posrnd * self.table.getSize(False)) self.pitr = Noise(self.pitrnd, add=1) self.durr = Noise(self.durrnd, add=1) self.discr = Choice(choice=self.discreet_value, freq=1000) self.pitch = CentsToTranspo(self.transp, mul=self.polyphony_spread) self.pospos = Sig(self.pos, mul=self.table.getSize(False), add=self.posr) initpan = [float(i) / (self.nchnls-1) for i in range(self.nchnls)] self.panr = Noise(self.pan, add=initpan) self.panw = Clip(self.panr, 0, 1) self.gr = Particle(table=self.table, env=self.grainenv, dens=self.dens*self.densr, pitch=self.pitch*self.pitr*self.discr, pos=self.pospos, dur=self.dur*self.durr, dev=self.dev, pan=self.panw, chnls=self.nchnls, mul=self.env*0.2 ) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.gr, self.osc, freq=10) self.out = Interp(self.gr, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self, index, value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.gr def discreet(self, value): self.discr.choice = value Interface = [ cfilein(name="sndtable", label="Audio"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cgraph(name="grainenv", label="Grain Envelope", func=[(0,0),(0.5,1),(1,0)], table=True, curved=True, col="purple1"), cslider(name="dens", label="Density per Sec", min=1, max=1000, init=100, rel="log", gliss=0, res="int", unit="x", col="blue1"), cslider(name="transp", label="Transpose", min=-4800, max=4800, init=0, rel="lin", unit="cnts", col="red1"), cslider(name="pos", label="Grain Position", min=0, max=1, init=0, func=[(0,0),(1,1)], rel="lin", unit="x", col="purple1"), cslider(name="dur", label="Grain Duration", min=0.0001, max=1, init=0.1, rel="log", unit="sec", col="orange1"), cslider(name="dev", label="Start Time Dev", min=0.0001, max=1, init=0.01, rel="log", unit="x", col="green1"), cslider(name="pan", label="Panning Random", min=0, max=1, init=0, rel="lin", unit="x", col="green3"), cslider(name="dnsrnd", label="Density Random", min=0.00001, max=1, init=0.001, rel="log", unit="x", col="blue2", half=True), cslider(name="pitrnd", label="Pitch Random", min=0.00001, max=0.5, init=0.001, rel="log", unit="x", col="red2", half=True), cslider(name="posrnd", label="Position Random", min=0.00001, max=0.5, init=0.001, rel="log", unit="x", col="purple2", half=True), cslider(name="durrnd", label="Duration Random", min=0.00001, max=1, init=0.001, rel="log", unit="x", col="orange2", half=True), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cgen(name="discreet", label="Discreet Transpo", init=[1], col="red1"), cpoly() ] cecilia5-5.4.1/Resources/modules/Time/Pelletizer.c5000066400000000000000000000125341372272363700221100ustar00rootroot00000000000000import random class Module(BaseModule): """ "Another granulation module" Description A granulation module where the number of grains (density) and the grain duration can be set independently. Useful to stretch a sound without changing the pitch or to transposition without changing the duration. Sliders # Transpose : Base pitch of the grains # Density of grains : Number of grains per second # Grain Position : Grain start position in the position # Grain Duration : Duration of the grain in seconds # Pitch Random : Jitter applied on the pitch of the grains # Density Random : Jitter applied on the density # Position Random : Jitter applied on the grain start position # Duration Random : Jitter applied on the duration of the grain # Filter Freq : Cutoff or center frequency of the filter (post-processing) # Filter Q : Q of the filter Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Grain Envelope : Emplitude envelope of the grains Popups & Toggles # Filter Type : Type of the post filter # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Discreet Transpo : List of pitch ratios # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) pitrnds = self.polyphony_spread * self.nchnls self.t = self.addFilein("sndtable") self.drng = self.densrnd*self.dens self.densr = Randi(-self.drng, self.drng, freq=.2, add=self.dens) self.posr = Noise(self.posrnd*self.t.getSize(False)) self.pitr = Noise(self.pitrnd, add=pitrnds) self.discr = Choice(choice=self.discreet_value, freq=1000) self.pitch = CentsToTranspo(self.transp, mul=self.pitr) self.pospos = Sig(self.pos, mul=self.t.getSize(False), add=self.posr) self.durr = Noise(self.durrnd*self.dur, add=self.dur) self.gr = Granule(self.t, self.grainenv, dens=self.densr, pitch=self.pitch*self.discr, pos=self.pospos, dur=self.durr, mul=self.env* 0.1) self.gro = Biquadx(self.gr, freq=self.cut, q=self.filterq, type=self.filttype_index, stages=2, mul=self.polyphony_scaling) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.gro, self.osc, freq=10) self.out = Interp(self.gro, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self,index,value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.gr def filttype(self, index, value): self.gro.type = index def discreet(self, value): self.discr.choice = value Interface = [ cfilein(name="sndtable", label="Audio"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cgraph(name="grainenv", label="Grain Envelope", func=[(0,0),(0.5,1),(1,0)], table=True, curved=True, col="purple1"), cslider(name="transp", label="Transpose", min=-4800, max=4800, init=0, rel="lin", unit="cnts", col="red1"), cslider(name="dens", label="Density of grains", min=1, max=250, init=30, rel="log", unit="x", col="purple1"), cslider(name="pos", label="Grain Position", min=0, max=1, init=0, func=[(0,0),(1,1)], rel="lin", unit="x", col="blue1"), cslider(name="dur", label="Grain Duration", min=0.001, max=10, init=0.2, rel="log", unit="sec", col="orange1"), cslider(name="pitrnd", label="Pitch Random", min=0.0001, max=0.5, init=0.0005, rel="log", unit="x", col="red2",half=True), cslider(name="densrnd", label="Density Random", min=0.0001, max=1, init=0.0005, rel="log", unit="x", col="purple2",half=True), cslider(name="posrnd", label="Position Random", min=0.0001, max=0.5, init=0.0005, rel="log", unit="x", col="blue2",half=True), cslider(name="durrnd", label="Duration Random", min=0.0001, max=1, init=0.0005, rel="log", unit="x", col="orange2",half=True), cslider(name="cut", label="Filter Freq", min=100, max=18000, init=20000, rel="log", unit="Hz", col="green1"), cslider(name="filterq", label="Filter Q", min=0.5, max=10, init=0.707, rel="log", unit="Q", col="green2"), cpopup(name="filttype", label="Filter Type", init="Lowpass", col="green1", value=["Lowpass","Highpass","Bandpass","Bandstop"]), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cgen(name="discreet", label="Discreet Transpo", init=[1], col="red1"), cpoly() ] cecilia5-5.4.1/Resources/modules/Time/Stutterer.c5000066400000000000000000000106411372272363700217670ustar00rootroot00000000000000import random class Module(BaseModule): """ "Stuttering an audio signal" Description This module reads successive segments extracted from a sound loaded in memory. The start position of each segment is chosen according to a step factor given in milliseconds. Sliders # Transpose : Overall transposition, in cents, of the segments. # Segment Duration : Duration of the segments in ms. # Start Pos Step : Starting point offset (from previous start position) in ms. # Crossfade Dur : Crossfade duration as a percentage of the segment duration. # Segment Dur Rand : Amplitude of a random applied to the segment durations. # Pos Step Rand : Amplitude of a random applied to the starting point offset. # Initial Start Pos : The position in the sound memory for the first segment. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Loop Mode : Sets the reading direction of the segments, either forward, backward or back-and-forth. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.table = self.addFilein("table") self.tableDuration = self.table.getDur(False) self.segmentStartPos = [] self.segmentDuration = [] self.reader = [] self.trigFunc = [] self.pitch = CentsToTranspo(self.transp, mul=self.polyphony_spread) for i in range(self.number_of_voices): self.segmentStartPos.append(Sig(self.tableDuration * self.initpos.get())) self.segmentDuration.append(Sig(self.dur.get() * 0.001)) self.reader.append(Looper(self.table, pitch=self.pitch[i], start=self.segmentStartPos[-1], dur=self.segmentDuration[-1], xfade=self.xfade, mode=self.loopmode_index, xfadeshape=1, startfromloop=True, interp=4, autosmooth=True)) self.reader[-1].appendFadeTime(True) self.trigFunc.append(TrigFunc(self.reader[-1]["trig"], self.prepare, arg=i)) self.out = Mix(sum([read for read in self.reader]), voices=self.nchnls, mul=self.env*self.polyphony_scaling) def prepare(self, voice=0): durationRandom = random.uniform(-self.durrnd.get(), self.durrnd.get()) * 0.9 centralDuration = self.dur.get() * 0.001 centralDuration += centralDuration * durationRandom self.segmentDuration[voice].value = centralDuration stepRandom = random.uniform(-self.steprnd.get(), self.steprnd.get()) * 0.9 step = self.step.get() * 0.001 step += step * stepRandom newStartPos = self.segmentStartPos[voice].value + step if newStartPos < 0: newStartPos = self.tableDuration - self.dur.get() * 0.001 elif newStartPos > self.tableDuration - centralDuration: newStartPos = 0 self.segmentStartPos[voice].value = newStartPos def loopmode(self, index, value): [read.setMode(index+1) for read in self.reader] Interface = [ cfilein(name="table"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="transp", label="Transpose", min=-1200, max=1200, init=0, rel="lin", unit="cnts", col="red1"), cslider(name="dur", label="Segment Duration", min=10, max=500, init=200, unit="ms", col="green1"), cslider(name="step", label="Start Pos Step", min=-250.0, max=250.0, init=5, unit="ms", col="purple1"), cslider(name="xfade", label="Crossfade Dur", min=0, max=50, init=5, unit="%", col="orange2"), cslider(name="durrnd", label="Segment Dur Rand", min=0, max=1, init=0, unit="x", half=True, col="green2"), cslider(name="steprnd", label="Pos Step Rand", min=0, max=1, init=0.01, unit="x", half=True, col="purple2"), cslider(name="initpos", label="Initial Start Pos", min=0.0, max=1.0, init=0, unit="%", col="grey"), cpopup(name="loopmode", label = "Loop Mode", init= "Forward", value=["Forward","Backward", "Two Ways"], col="green1"), cpoly() ]cecilia5-5.4.1/Resources/modules/Time/UltimateGrainer.c5000066400000000000000000000167571372272363700231000ustar00rootroot00000000000000import random class Module(BaseModule): """ "A state-of-the-art granulation processing module" Description This module offers more controls than the classic granulation module. Useful to generate clouds, to stretch a sound without changing the pitch or to transpose without changing the duration. It features a grain filter with control over type, frequency and Q for each grain. `Density per Second * Grain Duration` defines the overall overlaps. Sliders # Density per Sec : How many grains to play per second # Transpose : Overall transposition, in cents, of the grains # Grain Position : Soundfile index # Grain Duration : Duration of each grain, in seconds # Start Time Dev : Maximum deviation of the starting time of the grain # Filter Freq : Filter cutoff or center frequency, chosen at the beginning of the grain # Filter Q : Filter Q, chosen at the beginning of the grain # Panning Random : Random added to the panning of each grain # Density Random : Jitter applied to the density per second # Pitch Random : Jitter applied on the pitch of the grains # Position Random : Jitter applied on the soundfile index # Duration Random : Jitter applied to the grain duration # Filter Freq Random : Jitter applied to the grain's filter cutoff or center frequency # Filter Q Random : Jitter applied to the grain's filter Q Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance # Grain Envelope : Emplitude envelope of the grains Popups & Toggles # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Filter Type : Type of the grain's filter (Lowpass, Highpass, Bandpass, Bandstop, Allpass) # Filter Freq Ratio : List ratios used to randomize grain's filter frequency # Discreet Transpo : List of pitch ratios used to randomize grain's transposition # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.table = self.addFilein("sndtable") self.densr = Randi(-self.dnsrnd, self.dnsrnd, freq=1, add=1) self.posr = Noise(self.posrnd * self.table.getSize(False)) self.pitr = Noise(self.pitrnd, add=1) self.durr = Noise(self.durrnd, add=1) self.freqr = Noise(self.ffrnd, add=1) self.qr = Noise(self.fqrnd, add=1) self.filterfreq = Choice(choice=self.ffchoice_value, freq=1000, mul=self.ffreq) self.discr = Choice(choice=self.discreet_value, freq=1000) self.pitch = CentsToTranspo(self.transp, mul=self.polyphony_spread) self.pospos = Sig(self.pos, mul=self.table.getSize(False), add=self.posr) initpan = [float(i) / (self.nchnls-1) for i in range(self.nchnls)] self.panr = Noise(self.pan, add=initpan) self.panw = Clip(self.panr, 0, 1) self.gr = Particle2(table=self.table, env=self.grainenv, dens=self.dens*self.densr, pitch=self.pitch*self.pitr*self.discr, pos=self.pospos, dur=self.dur*self.durr, dev=self.dev, pan=self.panw, filterfreq=self.filterfreq*self.freqr, filterq=self.filterq*self.qr, filtertype=self.filtertype_index, chnls=self.nchnls, mul=self.env*0.2 ) self.osc = Sine(10000,mul=.1) self.balanced = Balance(self.gr, self.osc, freq=10) self.out = Interp(self.gr, self.balanced) #INIT self.balance(self.balance_index, self.balance_value) def balance(self, index, value): if index == 0: self.out.interp = 0 elif index ==1: self.out.interp = 1 self.balanced.input2 = self.osc elif index == 2: self.out.interp = 1 self.balanced.input2 = self.gr def filtertype(self, index, value): self.gr.filtertype = index def ffchoice(self, value): self.filterfreq.choice = value def discreet(self, value): self.discr.choice = value Interface = [ cfilein(name="sndtable", label="Audio"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cgraph(name="grainenv", label="Grain Envelope", func=[(0,0),(0.5,1),(1,0)], table=True, curved=True, col="purple1"), cslider(name="dens", label="Density per Sec", min=1, max=1000, init=100, rel="log", gliss=0, res="int", unit="x", col="blue1"), cslider(name="transp", label="Transpose", min=-4800, max=4800, init=0, rel="lin", unit="cnts", col="red1"), cslider(name="pos", label="Grain Position", min=0, max=1, init=0, func=[(0,0),(1,1)], rel="lin", unit="x", col="purple1"), cslider(name="dur", label="Grain Duration", min=0.0001, max=10, init=0.15, rel="log", unit="sec", col="orange1"), cslider(name="dev", label="Start Time Dev", min=0.0001, max=1, init=0.01, rel="log", unit="x", col="purple3"), cslider(name="ffreq", label="Filter Freq", min=20, max=18000, init=10000, rel="log", unit="Hz", col="green1"), cslider(name="filterq", label="Filter Q", min=0.5, max=20, init=0.7, rel="log", unit="x", col="green2"), cslider(name="pan", label="Panning Random", min=0, max=1, init=0, rel="lin", unit="x", col="purple4"), cslider(name="dnsrnd", label="Density Random", min=0.00001, max=1, init=0.001, rel="log", unit="x", col="blue2", half=True), cslider(name="pitrnd", label="Pitch Random", min=0.00001, max=0.5, init=0.001, rel="log", unit="x", col="red2", half=True), cslider(name="posrnd", label="Position Random", min=0.00001, max=0.5, init=0.001, rel="log", unit="x", col="purple2", half=True), cslider(name="durrnd", label="Duration Random", min=0.00001, max=1, init=0.001, rel="log", unit="x", col="orange2", half=True), cslider(name="ffrnd", label="Filter Freq Random", min=0.00001, max=1, init=0.001, rel="log", unit="x", col="green1", half=True), cslider(name="fqrnd", label="Filter Q Random", min=0.00001, max=1, init=0.001, rel="log", unit="x", col="green2", half=True), cpopup(name="balance", label = "Balance", init= "Off", col="blue1", value=["Off","Compress", "Source"]), cpopup(name="filtertype", label = "Filter Type", init= "Lowpass", col="green1", value=["Lowpass","Highpass", "Bandpass", "Bandstop", "Allpass"]), cgen(name="ffchoice", label="Filter Freq Ratio", init=[1], col="green1"), cgen(name="discreet", label="Discreet Transpo", init=[1], col="red1"), cpoly() ] cecilia5-5.4.1/Resources/splash.py000066400000000000000000000071621372272363700170370ustar00rootroot00000000000000""" Copyright 2019 iACT, Universite de Montreal, Jean Piche, Olivier Belanger, Jean-Michel Dumas This file is part of Cecilia 5. Cecilia 5 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. Cecilia 5 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 Cecilia 5. If not, see . """ import wx, sys, os from .constants import * def GetRoundBitmap(w, h, r): maskColour = wx.Colour(0, 0, 0) if sys.platform == "darwin": shownColour = wx.Colour(5, 5, 5, wx.ALPHA_TRANSPARENT) else: shownColour = wx.Colour(5, 5, 5) b = wx.EmptyBitmap(w, h) dc = wx.MemoryDC(b) dc.SetBrush(wx.Brush(maskColour)) dc.DrawRectangle(0, 0, w, h) dc.SetBrush(wx.Brush(shownColour)) dc.SetPen(wx.Pen(shownColour)) dc.DrawRoundedRectangle(0, 0, w, h, r) dc.SelectObject(wx.NullBitmap) b.SetMaskColour(maskColour) return b def GetRoundShape(w, h, r): return wx.Region(GetRoundBitmap(w, h, r)) class CeciliaSplashScreen(wx.Frame): def __init__(self, parent, img, callback): display = wx.Display(0) size = display.GetGeometry()[2:] wx.Frame.__init__(self, parent, -1, "", pos=(-1, size[1] // 6), style=wx.FRAME_SHAPED | wx.BORDER_SIMPLE | wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP) self.Bind(wx.EVT_PAINT, self.OnPaint) self.SetBackgroundColour(wx.Colour(0, 0, 0, wx.ALPHA_TRANSPARENT)) self.callback = callback self.bmp = wx.Bitmap(os.path.join(img), wx.BITMAP_TYPE_PNG) self.w, self.h = self.bmp.GetWidth(), self.bmp.GetHeight() self.SetClientSize((self.w, self.h)) if wx.Platform == "__WXGTK__": self.Bind(wx.EVT_WINDOW_CREATE, self.SetWindowShape) else: self.SetWindowShape() dc = wx.ClientDC(self) dc.DrawBitmap(self.bmp, 0, 0, True) wx.CallLater(2500, self.OnClose) self.Center(wx.HORIZONTAL) if sys.platform == 'win32': self.Center(wx.VERTICAL) wx.CallAfter(self.Show) def SetWindowShape(self, *evt): r = GetRoundShape(self.w, self.h, 17) self.SetShape(r) def OnPaint(self, evt): w, h = self.GetSize() dc = wx.PaintDC(self) dc.SetPen(wx.Pen(wx.Colour(0, 0, 0, wx.ALPHA_TRANSPARENT))) dc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, wx.ALPHA_TRANSPARENT))) dc.DrawRectangle(0, 0, w, h) dc.DrawBitmap(self.bmp, 0, 0, True) dc.SetTextForeground("#333333") font, psize = dc.GetFont(), dc.GetFont().GetPointSize() if sys.platform != "win32": font.SetFaceName("Monaco") if sys.platform.startswith("linux"): font.SetPointSize(psize - 2) else: font.SetPointSize(psize) dc.SetFont(font) dc.DrawLabel("Cecilia %s" % APP_VERSION, wx.Rect(280, 185, 200, 15), wx.ALIGN_RIGHT) dc.DrawLabel("Spirit of the project: Jean Piche", wx.Rect(280, 200, 200, 15), wx.ALIGN_RIGHT) dc.DrawLabel("Programmed by: Olivier Belanger", wx.Rect(280, 215, 200, 15), wx.ALIGN_RIGHT) dc.DrawLabel(APP_COPYRIGHT, wx.Rect(280, 230, 200, 15), wx.ALIGN_RIGHT) def OnClose(self): self.callback() self.Destroy()cecilia5-5.4.1/doc-en/000077500000000000000000000000001372272363700143605ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/build.py000066400000000000000000000003661372272363700160360ustar00rootroot00000000000000import os os.system("sphinx-build -a -b html ./source build") rep = input("Do you want to upload to ajax server (y/n) ? ") if rep == "y": os.system("scp -r build/* jeadum1@ajaxsoundstudio.com:/home/jeadum1/ajaxsoundstudio.com/cecilia5doc") cecilia5-5.4.1/doc-en/source/000077500000000000000000000000001372272363700156605ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/_static/000077500000000000000000000000001372272363700173065ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/_static/Cecilia5.ico000066400000000000000000005510201372272363700214230ustar00rootroot00000000000000dB00hZC I(LNDM00 Ƞhp (خ`` @@ (BK00 %Ѝ  x  hPNG  IHDR\rf pHYs+ IDATx][v$; ιeaWx,?:랞qzdnq|w]8㽵F; q"k?׽ߖKM( G]G G#_jAGk8Bw? G-woo&XiD\p ._@" gqi{й #!yuEbv@YB >U^dB3('ʣ`%X;-'{}+%ʀ3 *Cim.- Bh}hdEi|(hW@b5PFLմ湍)g`91\H:fsMiEj*w/uEeU >$kGd&C4!q?ZcdsXQѿV@ sǓG<,϶b* ۭ~h\+=>p CDK*~裓rAF]{PBƣ>\ᝥnV"i,)Hz_;~ee$PIDQ;Z$ }- 5M*Ĝ˰;^/% EdPlWL㾚oy [@ev7 ۧUަ yhT&jƥk), }h(~֨✑hBhI`SXDcKR]Ưyn>+4PD ɗULmVBRCkA(5-CO R_C֊z+d;b@aU!RR1ӶnWN<' ę!`ff19Zhk] El.+,i `;+D * Br%^t%-5v/oLcb B\y4D1-oOmzOd[dX hD)Z_/KУ{dTd6VG8{M#1")o&w$vLP^ yz"{:$k*ik6 H X]] :"{k;aV:;-9QjR'j87AkF^Te a5d~*x]P"H4fTr XVR2ﻁë+@%0z2®1#QL*Oolg nJ${ui - <"KkfH_4;?v^??CiD IG ܩ){[g8n`k, FJAh6x(&Py~\݆dہ5^n^hLFñHw2ˈ5dO<,u]/`N6Oƞڅ󲳋U§@ߊWgWA@{50T?^OyuFH'Cp%f)X@0{#zB3\^]B\gU3oUښۏz?8>=<{Ҍ><r/8} %s}=f]b?1?GS1MkLLN*r=|SCyVx?Nn6"vWxwQDo=3$M'ld?7=xyQqHd: ߙƯЗœj?eWotR,H.AY=O]B[m ~ޞ_/>~ ЁV3>x$9Gsަ$F  ,{i0~nP>7f?_zqP1s~@Xsw a41Bg|,'ho3<')#~q}$`GCp1Nx=5`@p 蔣siGFϤJA;qDuwàF7'!#{'N4,})7g &}*QBpͬf"h$0 4'eBF39JF>#^z|m.`;@CLREZX-τG+i|T#oNw>b>82dɏEXggnٿf)UN^Cm}x|Ds.;L>1,׈>akP%gR{|h&R&"fc$0R[VeC즠,]aۦ@g g𧯽OyFz:fi&/&*gJAH %nG~׫WT a/8%f4X*(GQ]qV[8YG@8Jl9#KQ*#iR׸eE/#r#B^Gc}뙋t$FҘX. ؝|ZՂzoB׮"LEFh(bCvMtP̦SD 0Bz |U͇sfSV|{9#.aMs`0 IF^782JC|d?JiwnBM`ɱUssC*g "1n/ڭ N?k$Gl |B[i&sT{!xG9al}ݲ~c͸]f?]f$I/p(!qH"ͥ/,?BnM7%aӦ.+y( Ћ2 O_>0HVyM^OwMc4Z< MEG?$ڌ|~>6Sq n#.PVN;je'Q{?r{o$cf1 ˰DYHYww~#xQM@i6  wञZ촬*%67uJ@S .+/!yj P4HjEI!Ojv}449聍,"fu, Xעm ~kvWqC!ѐp4h~;u/ 3C$yU@@! ! 7@37A;In;v?$Qᎌ1?*ENV?\UȐGK1Oe[%լcQٻ-\bMxU-檱i,#Y1dDp FU R"h#uPc_`en2+!(wȵCKa.H,>Lzۀ)Lc}UiR`s!U \X`HQeX&[f.>"SDP[Q=+l,ѡˆKYIW:1}9DӢ^צ-Xd֚cl%T VU!Ok7vB<55y5YwSoL8#! T0ʨiW<$qZҩH!PHT uI\UIlæw-$7HKT[(@h,RRY7K^򹆔R*U~s"BhmfZzL,I?H5t%Mց `8[~P^SSIPb;t29.4s>fH#w1+*"EzyZ9?^\BaD?e_DDNnһ0Ἱ@DMfiE.$XLJh Ic+7 IDATSmUAemܲ;2B3^ꚉD2 <#x yO)NZ% s3M< 1<82!C/D_]pVKW#Qjw_ψwм͵frlH%\j]d!lEcOwP9LT%S֥d63[a'jكb Ǯ*B#l#AQ:H a$<_ÏCp@܎aFXfiqZ}sy(3I+Zkeŏ0*45q ؑufƙ,)R_*rֳP%p?>oG8PB\(/S `-eLI>,H6KLa/x$LЬslwfB+gFA-B4YaKY?DDOePN/i_ahE%wRTqV#x-=n#x:}HxǻDҵDOU}:b#! V/xP~DVxbZ[+]Yf_-J.&2ٓh!*l3 3s30)4|6럹Oka;-1l&2ED*9t`Gu>\ 4T5:1hd.Bj %rNxخmh҂Xɯ՘|~m;x]aȐUbtSt & H, bp(iHlXH>LhSr>WpDzR5pv`4+@6rDLء!(A`+ڈ(gUn**K hi+++M zvWgw ?ӆvM4JȘ3 o.OzMD=VAd\q}tJ*?۩'"HB8^Yy^giybJiF)50 =T29+~+d:!A"jE-wx'Ǩ_񰀞kpym0D_Q9(UA'HmR`::gP:[px9&܊6eG %@N]H*J])`F&H%Ԟ?4=sDD"5HjPudPvRS1+AZw rf `#彳ZdOfWY? Ȗ>QA>GJj ?!)zBcdԖ^U켂:`|>3ߴoyo.&܇qO \4s vFrAMOuqz=͸qcHcWƮ2hcxK$jٺ|~7cHI osafVe~ W޸XyᇪdGyqY*-ow*n(R1SJBYT@ }:0UZJGFT,!2ҒZ׊#%,N0垰{Gml ZGkz{Vӡ|DD:e mXq|~-E`> E09R (hN?ַ48 6}\î]?, @-[qJޑ͞D4jHla04(nyŧ9_8DVJ Fytfe;rXWflgQŕ'AVcנ.΁,z||l MC(1gzMMY ^AI%@idtqH~շ'F07^LH?^y)IJ˒US>KzVT8ty1v?t#u{;T 񀓜DrT-!ĽrgRDx}#SRFxgz]/![k8ih9G HSHa__*6T38m"ĹF E ZGQ7ub(ӪtQ6MCLׇ]ӅkOJ'JFwXd諸Y-ƪ1^%#p Z>Z $OdX+%w -l5`ax@vS~:  c$#G$(#%F*0H5ODH{nl:̜%W~O:{ ,Uk6N]o2eԨVҗ=!{ymI/HDJeht"w>@a 98Ctc;ѳl p~% Vsf쮦,l Zڊkku:}rJN/*w$"U &b$razսHofë?,X t7ʈƘR޳JjGtVyR4;]q*ߩ) !2QrjdFZRZACGg>/di]RgSH|"jƮSt1ygQl,FJs^3Uy6S"4%*4k ?Q;Xj:X Tn*G#Eێ&l>͛jb$|cR_HϷAE%YNrksއ+/3Mkz8W@B_ni>~49 gyK(`SKӫ|;) 8LzƣF.Jwsg2rAtG`%B``+׮r(TE#i0 Ah:{b`TxɬsZ8A'E[ %g<=0[(jEթEUkKl@TYby|fsT/^s,)FDL XQn8s'no82Ƣ=bL. ifҪ@rsF6[%%9m/ 2+#bLkS:x kYUܭYq#=p^Lw#bH>釱m1E4TBJ>FقSF'Cn&x(WՁ'=X j;8 y՝v5yfElLjs/4h`HLNxZhFC.]FzKtxgesRi(%? P4+[yt2N:tVJn{gDd|ӈC6/;p>c"-~Y9cջ3^%Z1MMI*4xnA9$N@iD^S )"DY۞y5 9)р7ARnWBGNeEG.q? ˎuj@?m; Rdf })* ]xi\ԣ ቒTT70PG mPU0nŽF+p$@o(%h@+.DTz!IJd@FhY7 mY]85 {ǫx۳J7ɯ-@SLp#K<*p:2MQXH]~ ] ~)lILN9.hs4o6 @!^XcwVu톹mlvSH@a ǭۣk\e" < otJ  }=Nx]=9;zݐft" "5mW俔_Hu\;޹(iVUYIb^ IDAT0 K*]TGBht96J (/h|$i}fTPC"'; ^ݮ)@ g_gK7Ro5[Q\[/ "!";kM :8ݔPh:ޏ`-ps"{_@̻9|%DNjwuv"N;%e| T!+"9wfŘ7XiFX/M@YkEER{ ˏhQe|+0vx-c=vC$ʖIҮ-DF.)YZ2 ؋jK ;rԶTz.JHBa#*815%co/`I ƈlt w g?espe\3 I7Wj_p=^ @Kk2 +Rᗑ_p=6IENDB`(0`wwwwwwwxwwwwwwwwwwwwww~xhwwww~wwwwwwwwxwwwwxww|wpww~w|w|xgwwwwxwwwwwwgwwwxpww|wwwwww|wwwwxwwxwwwwxw~xwpwhww~w|vV|xhwwwwxxwxwwxhwVbGFRdvw~ww|ww|w|VBPrsCCCHwwwwvt`pwwww``vww|wpwwgBpgwwwwwwwwwwxhwt%%'wwwwegw|hwwwwwd6wwhhvwwwwwwxhgBCewxwwwwwvwwpwwwwt6w|~wxwwwxhhpwwwwda`wxw~w|wwwwwwpwwwt6gvwxwGFxwwxwwxwwwBC`wxwwwwwgwgw|wwwwxG$4ww|wwwwGwwxwwwwww`pcwxwxwhwvwxgvwwwxwaadwhhgxwvGwwwxLJww~wwB`pwwwwgww~w|wh~wwxaabw|xhhGwwwwwxwwwwabVwxgwwwgFwwwhgxwwxwt%$wwxw~wwwgwxwwww|vw|wxwwwww|wwpwwBpg~xvwvGww~wwvww`cFwwwxw~t7xgwwpwwwwt44wwwhwVGwwxhwwhwwwBCGwxwwwCgxhhwwwwwwwwt&whw$www|xpwwppdvwfwwwvwwxgwwwa pdWhWwwxwwxwwwarVwwwwwxhwwwwwwwwwwvw~wwpwxwwwwwwwwwwwwwwvww~w~xhwwwwpwxwwwwww|wwwwgxwwwwwwwxwwwpwwwww|wwwwxwxhxwwxwwwvwwwwwwwxwxx????( @wwwwwwwwwwwxhwwwwxh~wwwwxxwwwwww~wwpww~wwwwwwwwx~wwwwwww~wpwwwwddV|www~ww%gw$rwwwwwvpgwwwwwxwwww`wwwwfwwgpbVwwwwtwxpwwtpgwxwgwwwxwvwwwGwww~xw~^t6wwwwGwwwwwwwt$'ww|wgxwwxwt4wxwwgwwwwwwvwwxwgw~wwxxwt6gxwwVHwxw|A'|wwwwwwxwwVxwwwvwxwwpwxpawh~Vw|wp~wwvgwwwFwwwwwwadwvd4wwwxwp`eGwwwpwwwwwwwwxwxhWwwwwxhwwww~xgwwwxwwxwwvwww|wwwx??( wwwwwwwwxwxwwfvwgxgGtgpwFwxWwxwvwwwg|xvGwwvwwww~Wwwvgwww~xwBwwgwxwuggwpwwvwwwwwwxwwwwPNG  IHDR\rf pHYs+ IDATxs׹-{Q5Y۴#'rrrNr8%uNիwMnWc'1O-[e8=эFHİW $^߰@AAџ .,Y:G5@=799;J C3+NX,BVȲ ,pU{5-e?QoZ`YBx>!LMMQB0|C@,CV{7W4H9: B #W 2+qau&lbV`8Sr*K(P"WWWQ.(Iee5(1``Y^نmG36AT(ReU(8S06H\RXXX@ "JYː2r|?a!1G߹[\RP/XZzM4P.@deQ3S 33Ӕ(޽km@Uh9 ~āS3D8TUV.Brf( ի/a~~%uAeOwE :ȥLU.]ĵk(P>ܹsw8d)P2]F5z&:ĨoUjJ}b0^{f(tR7Q,š.黉8d)#EU!fp-JVLb|{ۑ8.r =KU%_++@QU0S=+< Mpd7n\YJ3|MSAg7+T/-qEZ_@ xٱ\rΚ8Lxu4P8ᗤ24MGlpQׯ}> XT$%J޷":Xb~G15( |u (bqqJ@QUپ'eL  i )Tq]kuE5G ]4)a|t?J}T*e` <5. "uclt?J}M\6͠h; z^( }<~غC%P>Neab0WnAch"FBu DQw{>G,1J-݁RGbQ @ @/zRŗw!2Xڟ;ӓ=)9s玵BY0<8>dw4:wyR. 9 SH OQO) N8_*ˈ]KP$uxJg%k @J.v:Y x?S<4LtKhmm(?ř Uד@޶ryr# Fצ ~0v`c7DjIT QH@8?9U]CTPT#sS\z{`ֻ@WښݯQRW@Gj q;4 S|m7 n"&%_ Ft Lѝ{26cbxIc [C݁V܃t%N"F`KE_1xA(mGTS4@Ԫ"'Oe?E'㨄Љ$1%`t%lӰ[&DŽogQq_IvJ w9]2;DNvZ ޸|KϜ~ZKO꥔KCɭ g~Lgܾg`5~ ,tHʸCqkvv+X\\bcW詾S=YA#㓊4zsx);_?}\GJ_kP ՠO2kP6YS''ݧ1E$=x._ޭ%GhZlTO;$_}ܹs:޶ԪR?O"V@"<,ܹs<5X\\Lq&J,Z7ItTp~ !9:CizzMv<3p* :G-Y/d)U.2M@Ű?0A6Ei aVͼa$~[dp ޷Uju_SdP(h2rrR -aypebXZS!(HT( <CQx4dӄI1Vv ),?[BnWc [fdͥB ,!0u,~#} 1x FM B0IT+n|QVoglI*dv30X(Be0lsq4b nυXUc@ts a)3 b<96wo["1\Kc9{[)dsiTwV aYpÊG+. x 4M(,"b}cB,s CMN#M"̅)ÌD²,{Yz:"2[x16 > _o7g?6e _WU&˞g qzQF|tu!!N_jxn䬂P l+=ӧ֟PB3PgYjY #1~sLϝ|]bSt5b`86 kP$Jd >GL,r>DqU|Ԏ3 ._?',..Zbx*&vHa}q`vHԿLϩsVw :,׈G=%\>'<&tqX`b@xp,XF,^tIA5@Y/#}f}ґfߟħ­NR)+:ՠN"};FstT8K< TnHH)y} ,gY}䜅:gj!"QU )80*NRN-n4f?%ӑ|~~aƯ)2?GOEҔ< ``8;h+)3`T/߽W3xc Y9" ,s!jвqZ︩N?Ϲ+mښ5??t,--YHy^w< {Vޕ]X`B2> QW9!Q ݪ)t]Hu}1~/ P̊ `$#"Uf"hda?oư FvcĎ _Hv>_ beӿ / ΂YIC!2s#C ">4yk*fwp "Vg k%(6! ?*0*\EW[Gu;ŲH| &2&7ll>H} 6xww%/Oc'w+>4$f`?D1TfRa*QZ 5D4zN:K,WU1ddַhBC}cg_QT UƮ,foD_FE^sD< q bnt^Ň&qmh.- rwh*V8ÔY^xSF.Bl gvvvܴ_#oEkO20בaF՛ǒEr?v?W8D^ucgtMQG7vK V{[#x,ꫯ]Ir&3!#4Pwg _MpqcxکͪM%}%ri8 Ƶ #;iPEʃiVĿ8}DXJ ϪW£|eY:'"F?60~ri0,^~ sQvCZ@Fq!8,iL jvou3N_g{G Us& k|arF5T^f;/uٖ9;?\*g44$0Eb^<}OcDXF}Ri 1>wo[G9-x$xۖ,ꀟe;?_Ȧw|E=^/ޕ5). JR} .tS7@o\y|V![‡w0zUrK_O vp.@G!uHGTG",?~zauNIm5;c(>/y7/l~E}r" Ԍ߻^U`AsVaţ/o3m%'m>/Ky|t~{!J!XՄnHWՏ_eT}ǎeii-8jY"Fn8N_)˄){x}!s~8;JR|4TQI_t%'q[=LDx_- yT=uY~={Df=Ν;xtla*+-\%Lu{ŻI{e?SmXfw6LJ`QerR.6rӐ ; -J`? ee;;d#/1~}F=9v\?Up\$43ŁrpfR9dw[4R)KB zQ |_Σc(?J=㟮ɑ9WqR;+lmٳ E'D|n@ 2Vᨯy)Eæ4M< 'XөJ{X^Kyߑ_WTǵsDU|~%%8B`"MˇJ7[Hʟ!tPcs8~x.@PZ RF"[`?x=*kx^ݩG!f7V@?,,( <@$yNU X&>r׿+ 8Uу;=| Vzw"xe*z5<|~$X+WLFe=hNy82/Rxu5HcNu K5Ny1"X #<&/bC{HC@"aOkL=+`XX$ \vh ѣG\F[ riHm,oD"l%vX]}=,tu|a  J<. 84 Ļ֖{wyCoNRY/uԢ8)%5:;@swY1RsԗgA$Ggu7KxWabnyKXOfV=ﰳJ,EU18ܙg[U篮>@X=Kvt.ix'쨯\D|MKi0g 1W07}TEđ4A j{KhZSUg߻7 : +^>א<`df)k!Ē8ȥ']aV>iM s ak_O#MA.!> U=Yjz<1۟1,XvM:?v3J@Hmx V& QGfuu@&Ti9u p(j*UVۊ#oD` "aYv,@XVZ3DqhT`/asw3PTzw{v-6#CtE#rL[ āppH w@?tq6?KC$aa>a.ɳ@>Ì+gD[EFC3/@h=G"W008L={f]|!]{Y&{UPO;+0 ,̱;7DZcE"+E"`9fE LMs5́#6X B4._\ @H \Ė7 ]?.TvX4N](:C" E0)Lctn?!e%%C7 (.ښ褐)+@<#|gy|{\@.TF $jkFV g Et50G Ur@%3<{PUa l,*D87:-±4$ MN*/_ #0L~Bt te{R0+M+yI1hvF?v`l,N (@$$M"D%:x15_Kz*(K͝w~vzVZROWZSE-J%b^ЈTFM>ž@P4Sip+2-0w"|k_nGs{){`RhR5~"+Ek۽E,ލP2L1EL IDATi@'ct5 e6LqȲwUE,;yl6] +iPX ! %0c zSڌOiƙF5߬; @  ePP"@@T)}`M3fjZ<^`-m#DUuTer6DElNeh e״8(sbJ,"4{K}p,1RaUI?P= 4,!G~˳qP_ptN*m-@4\8D*0^^ keê,7kwlfs+uP$`#f;o᧐Jy2cG"X;8[v`pܦkAT-o?`|s3W >LHR; O9U!1)ͣz4Ok;'߹-K?CsA$DDo78xŢ=(=.w0'pz0tb_܃VIa[4 I@ =p\h;deN^ *4D|)^y{4pX Ҁ^qb~_ i5*-c=mxXhB4 2L˄-7$(\ 3Ys10%`tXe;-0,fGc#6a+.e2H a{ 6,@X PD/G18݀di%ib :Q"Qnྕh%F*wRʓvbUBc M ?P ᰳtDFDȺ N ,f5zTzcXqxL,H/,ZuV-< eX7ELԺ"ٍ’ji t4~Ԋ7j V*J;MdpC7@bGꚈa 74@ Z2"Ŀ;]C: -eJ;EU $1Vd 1]-=,:^Ν poDN1PY.ѕ?_Ua(#T߆nT&F{0I&$ tv~'x  NW/6ϡpSQp_T1"@4H,ˁ-:ՋJpqmމ\'#l5ċBY_1PXC!E PWK0 Q[OZ'9 nA0#tHW:LruĢ"҅b\ D]Cw+u ;Oj=2"l p 1p,x@*l7P9;+QȕEO\7K ej ^g5SD{*syVxJ*PVP5l!0>/I\+k`n$KA i9*|59qw0fHh|,si triښP$R9 {:?7~,.O2d_j!7h[Ys `hh)c E<4Pgx.qP+{m( NXC^??-fGc&1h|4lN!OC,cS14|?뱱1krtPs;u<7wlwB \0`i Tc2#M܊ ggdx#_5:P"F0;; wNNE6ؚ@XI8NɔW!|l)R*ؾ3}-(ԫ4-Mb9MZ - :MߣPW=SsV][.FFG'*`rU0S4@{OlM *'Z:6( |?~_`׷oZsQoql0G{GV1,GQa ".AaaDV>7I.8qE\qlnA)I5* k8H{ip,]c\D4ˊs&#o~Op՗Z#`NX[|hcSa6e 6 k( tXh"l 9U4}6ϗ>f`i@cyP.4kט>R'<]T1G5E8sKU@_P| ?Au\|Fhp_?Rڜ43A8 rOiZBfcÇO<l@0ya) @\aVrIrG =h(pfv΃l=7ug:c^LÀЁqZgCMo3^@<| aX…*V^pZY\K>/xޢ[Ù `5FƍX=Yވox?ҙ2aUWZ]V 4]ž$/-T;Mvě8iqzz6E( VFtpgg|h245"{la#K"10d?_S_v?h 3& @iZqNO M+47sbX0iB0/=JF L 8MCa?' f{`M_} !SW G. wluڲBI Wn\}.LF8_f&lPPx{v3džK Ӷ*A?_*1J(Ct[c`}gA.*; mTvv`sX!|{_, h>lMO_ MrG&^{bOړO\$ FP3^)n&%%`<@EIO~zF^k?\ ن~W=S7hЃVG"IFyKɭt00.DOLO!f}>cTg I Ww T^ߐEwj HP !<^{y G4`BҡAqI*w\^ O>Z[W+6AĢJ= ,fgg- \^׶{yKmu?aE ̎{㗥< (o833)r4dX҃O=<䐀=p]']x jD'H ?Gx}׹'ja;n~ @6q]Ɯ ,,,0V*?(%wwvJcիpH[("8ȥq{okri|߰{}`ϛftˀ@\8w F/afhvHfleT@.-`Lu @S_0w Ȋ>'қHK>oQT xߟSܯ//|AK@ c]p*&Hmb'ɑܘv]S/>דN =-׃"87>K}dSm![n1́.W9|ߠ%L|: {ꩂg\UlB87y.~2Rϰ&C @Y9eVGx$d߮9g9^ 8~z/%_;./x`a0X%O v?,Q!,g":v\z( Xx |usxv}A®ݹw8F3z܂}(fwj#VKPL y؉ aYX E^xO;Ύ-uuwW+!g.횒 iቾQ81?qI'yvii&VWbs4P  C:gU *k_{1T{?}Tzg7y!= Ld\Jn߹~ؑ@*1H|[e"gϱ .a>p#2hdKaF4F$0 3CLavdwF881߿m䍾;'pRȥ7b/L! 8u:f *w`(1YL%F1|kYǿ9n x]~;-.ub9 ,2]d$:sݴ\ m1"uAcg )/_?Vo]or" ٤iߏHrW0DPP%~!f>FE}8U#!$0>68 ǔJn(JNAnuNIć&`(p (e Y.PB5TH_WvhjJGݮ tH`124>"@H"۲ByŴ=H;/w~淿{4+pJJ!Cw Yʾ'8@J rs!iy |2[$! |q>~lMm>Qߎ yfuuJmnBAqv#vgJ=wo[yA> Δ[OœxSmNO6=ܳk3[ E3~+l6kvO?3vR"<=5HAn1[;&gq vW@fE>OOhYf@SoNO 7wFvgRPM$Q0??dYۇiCQ(<( (mJ=om?5ؙ6JOӃ=kN=?YEO4 (޶vk(z OHu%#pSZU.7"Ftyc *nL W._HhA);ywp&<6BeFG+phEG޺wphvH(8{*9;wX-SfG]nݺňKKֳUo>%O̐KovR8ٱ>SeY!8uقVԙKpdyDK@vxs߹~ L^KPGN <5',):đh[0 IDATlB/nwTPT QEe>J-BPG %k Pyϧ_Z$'K@R!qnv#)Vzpr Q:i'%(N{z[B)ٽUpӾ>%:j`c QrbWWQ8֬/~ EUH#";Ћ0fFx~}Man"I$ KmFKO x8C@AC(d6#:bQ7n\﹂J'prAw>gJE/^K?1@39Q LS\8"-{)APb ӆjR!Mw|J''} k*Bzq0L`ff>%J v#DpzQZ0>:7w}J]58DbIښv{16:S)t),$I1> )n_@Wn?LT%D*&:=(=>+:A&E)?A,@G*BB/DG.AH*BJ,EJ/DL-FF2GJ1HK2IM3JI5MN6NK9NM9PO9RO<OQ8PQ9PU8TQ=VU=X[>YTA\VEYX@\YC]]C^ZE_\F`\F`YHb_Hd^Jg_Nh^NcaHgdKg`LibNjaPlbRmeQpfTriVtgXuiYwl[xm[zn\|n^|p^~p`rbtcsduexgwiyi{l}m~o~pwxqruuuvyz}xykd^^^^ckhOSYYYYYYYYYYSNg^SYYYYYYZYYYYYZYYYYX^SYYZYYZYZYYZZZZYZZZYYYXhSYYYZZYZYYZZYZZYZZYYZZYZZSf^YYYYZYYYYZYYYZYYZYYYZYYYZYYYXXYYZYZYZZYZZYZZYZZYZZZZZZYZYYZYSSYYZZYYYZYZZYZZZZZZZZZYZZZYZZYZZYSXYYYZYYYZYYYYZYYZYYZYZZZYZZYYYZYYYYS^YYYZZYYZYZZZZZZZZZZZZZZYZZZZZZYYZYYZXkYYZZZYZZZZZZYZYMC:8489BMYZZZYZZYZYYZZYfSYZZYYZZYZYZZJ4   ,?YYZZZZZYZZYS^YYYYYYZYZZZM# -8-% (YYYYZZZYYYYXYYYZYYZZZYY? CYYYZYY- YYZZZZYZZYZY^YYZYYZYYYZ9#YYYYYYYYZC CYZYYYZYYYZYXSYZYZZYZZZBYYYZZZZYZZY;:ZYZZZYZZYZZYlYZZYZYYZZSMYYZZYZZZZYYY ,YZYZZZZZYZYYgXYYYYYZYYY, 4YYYYYYYYYYYZYCYYZZZYZZZYYZXSYYZYZZYZYMYZYZZZZZZZZYZZ%9YZYZZZZZYZZYYYYZYZZYZY: YYZZZZYZZYZZZZYYYYZZZZYZZZZZYYnYYYYYYZYZ%8YYYYYZZZZZZZY##%(SYYYYZYYYZYYYkhYYZZYZZYZ?YZZZZZYZZYZYQYZ,8YYZZYZZZZZYYfcZYZYZZYYZGYZZYZZZZZZZZYYZZ:$ZYZZZZZYZZZZ]aYYYZYYYZYJYYZZZYYYYYYZZYYY(2YZYZYZZZYZYY]aYZZYYZZYZMYZZYZZZZZZZZY,#$MZYZZZZZYZZZZ]fYYZZYZZZY JYYZZZZYZZYZZY$GYYYZYZZYZZZZZYZ]iYZYYYYYYYCYZYYYZZZZZZYZ(CZYYYYZYZYYYZYZYfnZYZZZZZZZ- :YZZZZZYZZYZZY,#!#:ZYZZYZZZZZYZlYYZZYZZYZC,YZZYZZZZZZZZYCBBA:ZZZZZZZYZZZZSYYZZZZZZYYYZZZYYYYYYZZYYYYYYYYZYZZZYZYY^YYZYZZYZZ7BZZYZZZZZZZZYZ#YYZZZZZZZYZZYSlYZYZZZZZZY !YYZZZYZZYZZZQZZZYZZYZZZZZYiSYZYZZYZZZH 8ZYZZZZZZZZY4YZZZZZZZZYZZYaYYZYYZYYYY? ?YYZYZZYZYC#YYZYZYYYYZZY^YYYZZYZZZYZC8YYYZZZY?%YZZZZZZZZYZY`ZYZZZZZZZZYQ 4?CB8,YZZYZZYZZZY^SYZZYZZZYYYYYH% 6YYYZYZZZZYYmYYZZZZZZZZZZYYYC8%-6?QYYYZZYZZYZZYi`ZYYZZZYZYZZZZZYZZYYYYYYYYYZZZZZZZZZZ]XYZYYYZZZYZYYYZYYZZZYYZYYZYYZZYZYYYXXYZZZYZYZZZZZYZZYZZZZZZZZZZZZZZZZXYZZZZZZZZYZZZZZZZZYZZYZZZYZZYZZX`YYYYYZYZZZYZZYYYZZYZZYZZYYZY]mSYZZZZYZZZZZZZZYZZYZZZYZZYkaYYYZZZZYZZYZZZZZZZZZZY]aSYYYYZYYZZZYYZYYZY]m]QYYZZYZZZZYSXloifbbfio????( @7>%8>%:A&TT<XY?YVA\ZC\]A^ZE^]Da]GaYHb^He`JiaNj`PlcQmeRqfUrhVuiYwmZxmZzm]}o_|p^qatcsdvfvhyj}m~nqsuuvz}xx~QJD@@DJOQ@=>>>>>>>>>>>>>>>>>>>>>><J=>>>>>>>>>>>>>>>>>HD>>>>>>>>>>>>>>>>>>>>DD>>>>>>>>>>>>>>>>>>>>>>DJ>>>>>>>>>>>>==>>>>>>>>>>H>>>>>>>>6')6>>>>>>><>>>>>>=" &+& >>>>>>><>>>>>>= )>>>>>) 4>>>>>>>OD=>>>>= &>>>>>>>#->>>>>>><=>>>>>+ >>>>>>>>>#>>>>>>>>>>>>>> +>>>>>>>>>02>>>>>>>>QL>>>>>6 6>>>>>>>>##->>>>>>>>JF>>>>>0 >>>>>>>>>->5>>>>>>>FD>>>>>+ >>>>>>>>>>>4>>>>>>>@D>>>>>+ >>>>>>>>>!)>>>>>>>>@H>>>>>2 >>>>>>>>> >>>>>>>>>>FL>>>>>= >>>>>>>>>@>>>>>>>K>>>>>> 2>>>>>>>>>@6>>>>>>>>Q=>>>>>2 !>>>>>>>>2>>>>>>>>>D>>>>>> 2>>>>>>> >>>>>>>>@>>>>>>> 2>>>>>- >>>>>>>>Q@>>>>>>># !14- >>>>>>>@>>>>>>>>6# )>>>>>>>K>>>>>>>>>>>444<>>>>>>>>>KF>>>>>>>>>>>>>>>>>>>>>>FF>>>>>>>>>>>>>>>>>>>>FK>>>>>>>>>>>>>>>>>>K@>>>>>>>>>>>>>>@F<>>>>>>>>>CNJFDHM??( BJ,EL.FJ0GL0IK3IN2LO4NP6UU<[WB^ZD`ZGc]Ig_Ld`Ig`LjdNkcPnfRqgUphTvmXxn[{n^{p]}p_~q`sbxgxh}m~opqrtvyy}3,((,46&&&&&&&&40&&&''''&&&06&&'' &&&4&&''&'&6&&'"&&' ''&4,(''&'& &'',+&' '''&"&''(+&' &'''#'''(0''''''&'',4''#''&& '&''6('(  '&'&7'''' &''82'''''('&''17(''''''&86/(+/6( jeakfbida5hdaPfb`bca_xdb_eb`fb`fc`gc`fc`fb`eb`da_}db`hidaThcakfbkebida1da_pa_^gb`uketk{puxyyyyyyyyyyyyyyyyyyxu}qvlxngjdaa_^da_~ida@lfb kfbhdaB*;@'=B)>C*>C*>C*>C*=B);A(DG0QP:^YEldQ}p_|mxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysc`^ohdfc`StkexyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxwhndSUR=?C+D*CF.XU@pfTwhxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyulc`_lfcda_ٖuyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvgd]JCE/:@'9@&;B'=D(>E)?F)?G*@G*?G*?F*>F)>E(=D(E)>F)>F)>F)>E)=D(F)=D(;B':@&8?%7>$7=$6=$6=$7=$7>$8?%:A&;B'=D(>F)?G*@H+@H*@H*@G*?F)=E(%;A'=D(?F)@H+AI+BJ+BJ,BJ,AI+@H*>E)F)@H*AI+BJ,BJ+AI+@H+?G*=D(:@&TR=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzngfc`]b`^{pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvjZBC.7>%;B'>E)@G*AI+BJ+CK,CK,CK,BJ+AH+>F)78'/3.428!6<#9@&=D(?G*AI+BJ,CK,CK,BJ,AI+>E)F)AH+BJ+CK,CK-CK,AH+%%4:#NK9ufyyyyyyyyyyyyyyyy}oZSD.2/44;#9@&=E)@H*BJ+CK,CK,BI+>E)KL5yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywfb`mgcfc`=skexyyyyyyyyyyyyyyyyyyyyyyyyyyyyxg_N7<$9@%=D(@H*BJ+CK,CK,DL-DL-CK-CK,AI+?F);B'6=$<>)vjZxyyyyyyyyyyyyyyyyyyyqc;;*-239"8?%=D(@H*BJ+CK,BJ,?G*=C)}nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytkda_sa_^}qyyyyyyyyyyyyyyyyyyyyyyyyyyyyw]VE5;$:@&>E)AH+BJ,CK,CK-DL-DL-CK-CK,AI+?F*;B'6<$KJ7}nyyyyyyyyyyyyyyyyyyyyyyrJG7-228!8?%=D(AH+BJ,BJ,@H*;C'wkZyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvda_pid ida,nhcxyyyyyyyyyyyyyyyyyyyyyyyyyyywZTC6;$:A&>F)AI+BJ,CK,DL-DL-DL-DL-CK,BJ+?G*;B'6=$UQ>tyyyyyyyyyyyyyyyyyyyyyyyyvOJ;-239"9@%>E)AI+BJ,AI+=D(_ZFyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}qifc`_b`_yoyyyyyyyyyyyyyyyyyyyyyyyyyyyx^WF5;#:A&>F)AI+BJ,CK,DL-DL-DL-DL-CK,BJ+@G*wyyyyyyyyyyyyyyyyyyyyyyyyyyvLH8-24:":A&?F)AI+AI+>E)JK4yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyta_^pidkfbgb`䚅wyyyyyyyyyyyyyyyyyyyyyyyyyyyj`P4:#9@&>E)AI+BJ,CK,DL-DL-DL-DL-CK-BJ,AH+=D(8>%PN:vyyyyyyyyyyyyyyyyyyyyyyyyyyyyr?>-.45<#F)9@&CD/qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvh03!16 8>%=E)@H*?G*&ufyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyh^O-23:":A&?F)?F);B'rhVyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywfb`ohd jealfbxyyyyyyyyyyyyyyyyyyyyyyyyyxSO>4:":A'?G*BJ+CK,DL-DL-DL-DL-DL-DL-CK,AI+=E)8?%e]Lyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyw?>./47=$=D(>F)E)AI+CK,DL-DL-DL-DL-DL-DL-CK,BJ,?G*;B'DE/tyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}n`,139!:A&=D(F)BJ+CK,DL-DL-DL-DL-DL-DL-CK-BJ,@G*;B'HH3wyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}o`,117 6=$7>$8=&yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxmgkfb?gcaGzohyyyyyyyyyyyyyyyyyyyyyyyyyu;=)5;#E)9?%uiXyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx>=-,117 28!49$qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxneb`~da_yoyyyyyyyyyyyyyyyyyyyyyyyyyqfV17!9?%>F)BJ+CK,DL-DL-DL-DL-DL-DL-DL-CK,@H+?=.NI:rdyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxjdaohdjeaid`ꛆxyyyyyyyyyyyyyyyyyyyyyyyywi16!7=$=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-CK,BI+>E):@'ufyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxmglfc=hda5vlfyyyyyyyyyyyyyyyyyyyyyyyyyg]M17 9@&?F)BJ+CK,DL-DL-DL-DL-DL-DL-DL-CK,AI+$>E)BI+CK,DL-DL-DL-DL-DL-DL-DL-CK,BJ+>E)D*?D+>E)AH+JM3{n^yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxgc`pid c`^ؙwyyyyyyyyyyyyyyyyyyyyyyyyc[K16 9@%?F)BJ+CK-DL-DL-DL-DL-DL-DL-DL-CK,AI+=D(CF/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyse;@(?F*BJ,[XCteqwikbQ@F,DL-HO0ym\yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyphcqid"jeaid`꜇yyyyyyyyyyyyyyyyyyyyyyyyyMJ928!:A&?G*BJ,DL-DL-DL-DL-DL-DL-DL-DL-CK,AH+yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytf9=&;B'TR=xyyyythXCJ-IQ0MR5~pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxngjeb>,39";B'@H*CK,DL-DL-DL-DL-DL-DL-DL-DL-CK,@H+;B'f_Lyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvh7:&8<&pbyyyyyxHJ2HQ0LU2qiUyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyrjkfbThda4vmfyyyyyyyyyyyyyyyyyyyyyyyyx/34;"C*yiyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyXW?LU2MV3WX>yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|qfc`jeb`ulyyyyyyyyyyyyyyyyyyyyyyyyqb-26=$=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-DL-BJ,?F)>C*}nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{lIP2LU2JS1jcOyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rgcajebgulyyyyyyyyyyyyyyyyyyyyyyyyqb-26<$=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-DL-BJ,?F)=B)ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxsyyyyzkST;IR1IR1GM0zkyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysgcajfbavmyyyyyyyyyyyyyyyyyyyyyyyypb,26<#=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-DL-BJ,?F)=B)tyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyrSPD*xhyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyte@F+DL-~p`yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxneb`yjeb.ulfyyyyyyyyyyyyyyyyyyyyyyyyy?>-17 9@&?F*BJ,DL-DL-DL-DL-DL-DL-DL-DL-CK,@G*:A'ueyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyug@F,DL-|n^yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvljebclfbohcyyyyyyyyyyyyyyyyyyyyyyyyyNJ:058?%>E)BJ+CK,DL-DL-DL-DL-DL-DL-DL-CK,@H+;B'zm]yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvhAF,FN.|o^yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}qikfbPlfb gc`曆xyyyyyyyyyyyyyyyyyyyyyyyycZK.36=$=D(AI+CK,DL-DL-DL-DL-DL-DL-DL-CK,AH+E)NO8yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{m;@(BJ,HP0KT2KT2JS1IR1JR1JS1HQ0FM0yjyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxfb_pidda_zoyyyyyyyyyyyyyyyyyyyyyyyyyA@/058?%>E)BJ+CK,DL-DL-DL-DL-DL-DL-DL-BJ,?G*>D*uyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}p6:$;B'@H*CK,DL-DL-CK-DL-DL-CK-AH+tdyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyua_^gcaXsjyyyyyyyyyyyyyyyyyyyyyyyyycZK-26<#=,/57>$=E)AI+CK,DL-DL-DL-DL-DL-DL-CK,BJ+>E)RQ;yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyulflfc2b_^syyyyyyyyyyyyyyyyyyyyyyyyyk`Q+14:";B'@G*BJ,CK-DL-DL-DL-DL-DL-CK-BJ,@G*?D*~oyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxgb`pid eb`wwmyyyyyyyyyyyyyyyyyyyyyyyyyr14"06 8>%>E)AI+CK,DL-DL-DL-DL-DL-DL-CK,AH+F)LM5xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywi78&59$9<'kbQyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvlfc`qlfb fb_ᚅwyyyyyyyyyyyyyyyyyyyyyyyyy}p14"057>$=D(AI+CK,CK-DL-DL-DL-DL-CK-BJ,@H*>D)tcyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyg]N055;#6<#6;$uyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysjemgc1b`^}qyyyyyyyyyyyyyyyyyyyyyyyyyyf\M,039":A&?F)BJ+CK,DL-DL-DL-DL-DL-CK,AI+>E)US=xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyCB04:"9@&9@&=A*yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvc`^qiegc`S|piyyyyyyyyyyyyyyyyyyyyyyyyyyt;;*.35<#D)sbyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytf38"8>%%=D(AH+BJ,CK,DL-DL-DL-DL-CK,BI+>F)NO8wyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyYSB4:":A&=D(;B'US>yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvlflfc=b`^|qyyyyyyyyyyyyyyyyyyyyyyyyyyy]UF+028!9@%>E)AI+BJ,CK,DL-DL-DL-CK-BJ,AH+>D)ofSyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|n6:%8>$=D(>F);B'a[Hyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvc`^qiehdaFxmfyyyyyyyyyyyyyyyyyyyyyyyyyyyvEB2,13:":@&>F)AI+CK,CK,DL-DL-DL-CK,BJ+?G*@F+xhyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy[UD4;";B'?G*?G);B'jbOyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywmgca{mgcc`^՗vyyyyyyyyyyyyyyyyyyyyyyyyyyy}o88'-34:#:A&>F)AI+BJ,CK,DL-DL-DL-CK,AI+>F)JL4ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyrc5:$9?%>E)AH*?G*:A'rhVyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxngblgc%eb`tulyyyyyyyyyyyyyyyyyyyyyyyyyyyyug34#.34:#:A&>E)AI+BJ,CK,DL-DL-CK,BJ,AI+>E)UT=uyyyyyyyyyyyyyyyyyyyyyyyyyyyrDD07=$=D(@H*BI+?G*:A&{n^yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~ra_^lfcidaꚅxyyyyyyyyyyyyyyyyyyyyyyyyyyyyqc03!.34:":@&>E)@H+BJ+CK,CK-DL-CK,BJ,AH+=E)XU?tyyyyyyyyyyyyyyyyyyyyyyyyywRO=6<$E)SR;pyyyyyyyyyyyyyyyyyyyyyyyvXSA6=$;B'?F*BI+CK,BJ,?G*F)AH+BJ+CK,CK,CK,BJ+AH+>E)GJ1tcyyyyyyyyyyyyyyyyyyyyyrSP=7=$;B'?F)AI+CK,CK-BJ,?F)>C*yjyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzohgcaZa`^zoyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~pJF6,0055;#9@&=D(?G*AI+BJ+CK,CK,BJ,AI+?F)>E*d^JqyyyyyyyyyyyyyyyyyxpaEF08?%F)=C)~pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyub`^qidjeb'keaxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywg]N.2 -228!6=$:A&=D(?G*AH+BJ+BJ,BJ,AI+@G*=E)CH.ldQpyyyyyyyyyyyyyxteUR>9>&:A&=E)@H*BJ+CK,CK,DL-CK,BJ+>E);A(vyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyngidaXb`_vmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywiFC3,0.439"7=$:A&=D(?F)@H*AI+BI+AI+AH+?G*=D(?E+YV@wlZ{lxyyyyyyttdjbPII3:@':A'=D(?G*AI+BJ+CK,CK,CK,BJ,BJ+@H*%yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysc`_pidlfcfb_☃vyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxuhY<;+,1/439!6<$9@%;B'=E(?F)@G*@H*@H+@G*?F)=E)D*AE,OO9TS'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxqidjeb?da_i{phyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvreW>=--1.317 4:"7=$9@&;B'E)>F)?F)>F)>F)>E)=D(=D(E)?F)?G*@G*@G*@G*?G*>F)>E)=D(;B':A&9?%6=$39!8:'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyob`^ngcb`^~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx~oaRM=14",1.417 3:"6<#7>%9@&:A&;B'$6<#4:"28!16 05 /3 =<,yyyyyyyyyyyyyyyyyyyyyyyyyyyyyvhc`oidida2mfbwyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyystgYSM=46%.2-2/417 39!4:"5;#6=$7=$7>%8?%8?%8?%8?%8?%8?%8?%7>%7=$6=$5;#4;"39"28!06 05 15!>>,SN=i_P}n`}oyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyogeb`eda_~rjyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxtflaRTN>>>-.204!-2-3.4/50506 06 06 06 05/5/4.315!15!46$DC1WQAi_P~o`qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{pa_^rjeohcb_^}qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywvh~o`reWg]NcZJ^VGZSCWPAZSD_WHc[Ji_OrfW|n_se~pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvgb`ngc jeb'hc`痃vyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxsjehdaTfb`Xsjexyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyskca_b`_~riyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzob_^rje pida_^wmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyysda_qjeohcc`^ȍ|qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuhd`mgc.lfc da_ؐ~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvlfbjebEkfb,fb_ߒsyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvogcgcaTlfb1gc`syyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvohcgca\lfc2fb_ߑ~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvngbgca\kfb,da_׍|qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytkfagcaRlgc c`^LJwmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rgc`kfc?ohda_^~qixyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyync`^lgc*qieb`_sjevyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx}qia_^qiefc`Yhc`}qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytphcdb`skemgc(b`^~rjwyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxnea_jebFohdda_}lfb~ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuukeb`_ohdjeb3b`^|phvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvlda_hdaSqjdqidgc`gfb_wmxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx{plfbc`_pidngcdb`keayoxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx~rrjdc`_mgc,kfb&b`^lfbynxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx}qskeb`^ieb>kfb&a_^idaulvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywynngca_^ieb>nhcfb`tc`^wmg|qxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxs}qigb`ca_mgc+ohdiebGb`_hc`|pi}qwyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxstklfbb`^gcaYpidohdhdaRb`_eb_vlfwmsxyyyyyyyyyyyyyyyyyyyyyyyyyyxuyozohic`b`^hcacnhcqid kfb9fc`ub`^gb_sjesjyosvxyyyyyyyyyyyyyyywt{pukvlfid`a_^eb`kfbGqjepid mgc-idaXeb`a_^b`^gc`ogbulfyoh}qisjulvlvmultk~ri{ohvmfphcid`c`^`_^fb`hdaalfc6ohdngc pidkfb/jebCkfbPlfcYlgc_mgcbmgccmgc`lgc[lfcTkfbFlfc3qie#ohd?????????????(` ida hc`&hcaGgc`ghd`fb`idakealfblfbkebidafb`gc`fc`midaMida,jeahc`hdagc`Pfb`jeaske~rjyn~ruvwxxxxwvu~ryotkulfkebgc`gc`Yida idahda$eb`ridaxngyouxxxyxxyxxyxxyxxyxxxxv{p|pikebfb`hda1jeaidafb``ida|qi~rxyyyyyyyyyyyyyyyyyyyyyyyyyyxtskkebgc`qjebhdaeb`rje{pwyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyx}rvmfgc`jeb-idaea_wmgsxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxxu|qieb`ida,jeafb`|ulf쓀tyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyv{phfb`lfbidahdaKmgb֎}qxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxtrjegc`elfbkebeb_tkxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxxynhc`lfb!hda>mgcؒsyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvskegcaYlfbkebfb`ozphwyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxxulgc`ngclfbfb`wnxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx|qhc`mgckfbhca}qyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxtlfbmgc#kfbidaÒsxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyvngclfb)kfbjdaƔtxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxvohclfb)kfbida”tyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvnhcmgc"kfbhcasxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyvlfbngcjeaeb_|qxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxtgc`nhcfc`gvmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|qgc`mgbjeb5xngxxxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyulhcaYkeb kfbЙwyxxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxu~o}n~pvxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyxskemgc!eb_~syyyyyyyyyyyyyyyyyyyyyyyyyyyyyw|n{n^haNZVBNN7DG/;A(C*JK4WU?f_Kxm[zkvyyyyyyyyyyyyyyyyyyyyyyyyyyyvhc`mgchda=~rjyxyxxxyxxyxxyxxyxxyxxyxyxxyu~p`_ZFFH1;A';B'E)>E)>E)=D(=D(F)@G*AH+AI+@H*?F)=D(;B'9@&8?%7>$7>$8>%9@%:A&E)?G*@G*@G*?F)=E(>D)IK3d^Jtexyxxyxxyxxyxxyxxyxxyxxxrjemgcfb`hzoyyyyyyyyyyyyyyyyyyyyyyyyzkXT@:?';B'>E)@H*BI+BJ+BJ+AI+?F)E)@H+BJ+BJ+AI+@H*=E(F)AI+BJ,CK,CK,BJ+@H*=D(8>%5:#HG4j`PufsxxytxjodULI804!17 6=$;B'?G*BJ+CK,CK,BJ+=D(kcPyxxyxxyxxyxxyxxyxxyxxyx{phjeb,ea_z}rxxyxyxxxyxxyxxyxxyxxyxwhEF19?%>E)AH+BJ,CK,CK,CK,BJ+?G*;B'6<#OL9texxxyxxyxxyxykVP@.339!9@&?F)AI+CK,CK,@G*KM5yxxyxxyxxyxxyxxyxxyxxyxvfb`idarjd䜇xyyyyyyyyyyyyyyyyyyyyx{m^:?(:A'?G*BJ+CK,CK-DL-CK,BJ+?F)9@&=@*wjZxyyyyyyyyyyyyyy~oa8:(16 8?%>F)BI+CK,AI+=C)}nyyyyyyyyyyyyyyyyyyyyyyy}qijeb.fb`n|qyxxyxyxxxyxxyxxyxxyxxthX8=&;B'@H*BJ,CK,CK,CK,CK,BJ+?F*9@&GG2zlyxxyxxyxxyxxyxxxy|nA@.06 9?%?F)BJ+BJ+=D(wkZxxyxxyxxyxxyxxyxxyxxyxxugc`idamgbԛxyxxyxyxxxyxxyxxyxxyxxk[7<%;B'@H*BJ,CK,DL-CK,CK,BJ,@G*:A&HH3qxyxxyxxyxxyxxyxxxyx~p>>,17 :A&@G*BI+>F)a\GxxyxxyxxyxxyxxyxxyxxyxxxvmglfbgcaGvmyyyyyyyyyyyyyyyyyyyysd8<&;B'@H*BJ,CK,DL-DL-DL-CK,AH+;C'CE/~pyyyyyyyyyyyyyyyyyyyyyyk57%3:"E);@(ufyxxyxxyxxyxxyxxyxxxyxxytgY.37=$>E)?G*AE,txyxxyxxyxxyxxyxxyxxyxxyxkeblfbidaulf휇yyyyyyyyyyyyyyyyyyyxSO=6=$>E)BJ+CK,DL-DL-DL-DL-CK,@G*:@&jbPyyyyyyyyyyyyyyyyyyyyyyyyxMI917 :A&>F)%8?%pfTxyxxyxxyxxyxxyxxyxxyxxyxxkebmfbidaqidyyyyyyyyyyyyyyyyyyyqfV3:"%?G*CJ,CK,DL-CK,CK,DL-CK,AH+;B'shWyxxyxxyxxyxxyxxyxxyxxxyxxyxxxDA2(-,0k`Qxyxxyxxyxxyxxyxxyxxyxxyxx{pidaYfb`g}ryxxyxxyxyxxxyxxyxxrc28!;B'AI+CK,CK,DL-CK,CK,DL-CK,?G*=B)qyxxyxxyxxyxxyxxyxxyxxxyxxyxxy~pugYsexxyxxyxxyxxyxxyxxyxxyxxyxxvhcagc`vyyyyyyyyyyyyyyyyyy]VE5;#=E)BJ,CK-DL-DL-DL-DL-CK-BJ,=E)VS>yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxlfblfbgc`lfb˛xyxxyxxyxyxxxyxxyxx<=*8>%?G*CK,CK,CK,DL-CK,CK,CK,AI+F)QP:xxyxxyxxyxxyxxyxxyxxyxxxyxyk8=&CF/ryxxneSFN.LS3}nxxyxxyxxyxxyxxyxxyxxyxxy~rhdamhcaZ|qxyxxyxxyxyxxxyxxyxOK:5;#>E)BJ,DL-CK,CK,DL-CK,CK,BJ,>E)_ZFxxyxxyxxyxxyxxyxxyxxyxxxyxrYSBthXxyxx~pDK.LU2skVxxyxxyxxyxxyxxyxxyxxyxxyuidafc`o~syyyyyyyyyyyyyyyyyyCB06=$?F)CK,DL-DL-DL-DL-DL-DL-BJ+=D(ldQyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvJN3LU3ebJyyyyyyyyyyyyyyyyyyyyyyyyvgc`hca~txyxxyxxyxyxxxyxxyx;<*7=$?G*CK,DL-CK,CK,DL-CK,CK,BJ+$?G*CK,DL-CK,CK,DL-CK,CK,BI+$?G*CK,DL-DL-DL-DL-DL-DL-BI+~p`xyxxyxxyxxyxxyxxyxxyxxyxxyxlfbhda{tyyyyyyyyyyyyyyyyyyA@/5;#>E)BJ,DL-DL-DL-DL-DL-DL-BJ+E)neSxxyxxyxxyxxyxxyxxyxxyxxxyxxvjZBI,ngRyxxyxxyxxyxxyxxyxxyxxyxxyxxyxxytjebhdaE)BJ,CK,CK,DL-CK,CK,CK,@H*CG.xxyxxyxxyxxyxxyxxyxxyxxxyxxzl];B'CK-GP/HP0GO/GP0GO/EL.zkxxyxxyxxyxxyxxyxxyxxyxxyrjmgc&kebśxyxxyxxyxyxxxyxxyxxPL;28!D){lxyxxyxxyxxyxxyxxyxxyxxxyxxwiXTA\XD`[Gb]Ic^Jf_LXU@:A'vgxxyxxyxxyxxyxxyxxyxxyxxyskengc fb`vyyyyyyyyyyyyyyyyyytgY.38?%@G*CK,DL-DL-DL-DL-CK-BJ,>E)ukYyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuoatyyyyyyyyyyyyyyyyyyyyyyyxjeagc`^|qyxxyxxyxyxxxyxxyxxt47$4:"=D(BJ+CK,DL-CK,CK,DL-CK,?G*YWAxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyxxyxxugc`ida(~rjyxxyxxyxyxxxyxxyxxy[TE/59@&@H*CK,DL-CK,CK,DL-CK,AI+AF,ryxxyxxyxxyxxyxxyxxyxxxyxxyxxsrxyxxyxxyxxyxxyxxyxxyxxyxxyxxyokebPidangcלyyyyyyyyyyyyyyyyyyy{m03!5;#=D(BI+CK,DL-DL-DL-CK-BJ,>E)rhVyyyyyyyyyyyyyyyyyyyyyyyyyyyxj47$5:$UR?xyyyyyyyyyyyyyyyyyyyyyyyyyyxngmgceb_vxxyxxyxyxxxyxxyxxyx[TD/49?%?G*BJ,CK,CK,DL-CK,CK,@H*KM5vxxyxxyxxyxxyxxyxxyxxxyxxyxg]M39!8>%8=&wxxyxxyxxyxxyxxyxxyxxyxxyxxidahcaHwnxxyxxyxyxxxyxxyxxyxq46$39!;B'AH+CK,CK,DL-CK,CK,BJ+>E)wlZxxyxxyxxyxxyxxyxxyxxxyxxywBC/8>%;B'FH1yxxyxxyxxyxxyxxyxxyxxyxxyx~rhcarkeb qid囆xyyyyyyyyyyyyyyyyyyysgX-25<#=D(AI+CK,DL-DL-DL-CK,@H*GJ1tyyyyyyyyyyyyyyyyyyyyyyyy}o`4:#;C'=D(PO9yyyyyyyyyyyyyyyyyyyyyyyyyy|qimgc%fb`uxyxxyxyxxxyxxyxxyxxxUO?.37>$>E)AI+CK,CK,DL-CK,BJ,?F)b]HxyxxyxxyxxyxxyxxyxxxyxxxMK88?%>F)=E([WCyxxyxxyxxyxxyxxyxxyxxyxxywjeaida7tkxyxxyxyxxxyxxyxxyxxytA@//58?%>E)BI+CK,CK-CK,CK,AI+?F*ym[yxxyxxyxxyxxyxxyxxxyxxwj[6;#=D(@H*=E(b]Iyxxyxxyxxyxxyxxyxxyxxyxxy{pida_jebidaĚxyyyyyyyyyyyyyyyyyyyy}o::)05 8?%>E)AI+CK,CK,CK-CK,AH+BG,tdyyyyyyyyyyyyyyyyyyyy|n=@*;A'@H*AI+=D(kcQyyyyyyyyyyyyyyyyyyyyyyyyxrjenhcgcaZyoyxxyxyxxxyxxyxxyxxyxxzl:;*/57>$=D(AH+BJ,CK,CK,BJ,@H*BH-rbxyxxyxxyxxyxxyxxxyqHH49@&?G*BJ+BJ+-238!9?%=D(@H*BJ+BJ,BJ,AI+>F)SSF)@G*@H*@H*?F)=E)AF,QQ:a\Gg`LiaNd^JYVAHJ3=B);B'=E)@G*AI+BJ+BJ+AI+@H+?G*=D(8>%ueyxxyxxyxxyxxyxxyxxyxxy}qgc`|kebgc`vyyyyyyyyyyyyyyyyyyyyyyyyy{m\TE46$.428!6<#9?%;B'=D(>E)>F)>E)=E(=D(E)?F)?G*?G*?F)>E)=D(;B':@&8>%4;"16!ufyyyyyyyyyyyyyyyyyyyyyxmgcphd jeb)wmgxxyxxxyxxyxxyxxyxxyxxyxyxxysqeVKG715".417 4:"6<#8>%9@&:A&:A';B';B';B';B';B':A':A&9@&8?%7=$5;#39"27!27">?,QMg^N~o`~pxyxxyxxyxxyxxyxxyxxyxxyxtfb`nhclfcgc`uyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx|nsexk\qeVmbSh^Oj`QodTsgW{m^uf{mxyyyyyyyyyyyyyyyyyyyyyyyyyyyywngcohdkfb qid⚅xxxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxx{phlfb>hdaI|qixxxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxyxxwnhcaofc`owmyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}qgc`pidmgcfb`{pxyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxysidapidmgcfb`}qyxxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxyxxtidaohdnhcgc`}qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytjeaohdnhcfc`{pxyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxxysidaohdmgcfb`wmyxxyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxyxx|qidaohdmgcfc`o}qixyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxvmfb`pididaIqid㖂uyxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyxxwxnghdagohdlfb hdazpxxyxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxxyx~rlfbmgc4mgcfc`swmgvyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyw~qjfb`ohd lfb)hc`vmxxyxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxxyxzokebkfb=ngcidaPlfbӊzoxxxyxyxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyxxyxx~rqidhdaiohcmgcgcadmgb׉yoxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyx|qrjefc`zohdlfc hda[jeaĀskuxxxyxxyxxyxxyxxyxxyxxyxxxyxxyxxyvvmmgbhdammgcmgckeb7fc`qidwnvyxxyxxyxxyxxyxxyxxyxxxyxxywzoulfgc`jebFohcngc jebGgc`ngc~rj|qvxyyyyyyyyyyyyyyyyxv}rtkqidgc`jebUnhcmgclfc(ida^gc`kebske}qiwm{p~rsuvvutr|qwn~rjulflfbhc`idahlfb1ohcmgc mgc!jeb=jebVhdaljea{keblfblfbkfbjeb~hdankfbYkfbAmgc&ngc mgc????????(@ Bda_ea_$fb`Sjea}qidulfyoh{pi|qiyohulgrjelfbgc`Yeb`*eb`eb`fb`Yqid}rj{puxyyyyyyyyxv|qskskehcadfb`da_fb`>pidumvyyyyyyyyyyyyyyyyyyvwnrjegc`Ifc`da_gc`KulfǏ~rxyyyyyyyyyyyyyyyyyyyyyyytyohhdaZgc`gc`/skesyyyyyyyyyyyyyyyyyyyyyyyyyyyyuvmgida>fc`kfbzoxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|qmgcidahda&vlg˗vyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywzohjeb5idaHul훆xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxnkeb^idab{pyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rlfb|jeak}ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytlfbida`}ryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyytlfb|idaDzpyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy~rjeb^ida"tk꜇yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxnjeb5fb`tkfśxyyyyyyyyyyyyyyyyyyyyyyywrrwyyyyyyyyyyyyyyyyyyyyyyzphjebjeb|vyyyyyyyyyyyyyyyyyyt~q`haNWU?JL4@E+C)=D(?G*@H*?G*=D(;B'9@&9@&:@&;B'=D(?F)?F*>E)FJ1`\F{o^vyyyyyyyyyyyyyy|qjea>qidxyyyyyyyyyyyyyyxzm]GI2)PM:ZTCZTCPM;;=)28!7>$=D(AI+BJ+AI+=E)neRyyyyyyyyyyyyyyyvmghcafc`=}ryyyyyyyyyyyyyyqTQ=;B'@H*BJ,CK,BJ,?G*9?%OM9qbwyyyyxteRN<17!:@&@H*CK,BJ+LN5yyyyyyyyyyyyyyyuidaZrjexyyyyyyyyyyyyyzkCE/=D(BJ+CK,CK-BJ,>F)=A)wk[xyyyyyyyyy{m^69%8?%@H*BJ,>E)}nyyyyyyyyyyyyyyyxnhhdafb`.{qyyyyyyyyyyyyyzk?C,>E)BJ,CK,DL-CK,?G*?C+xhyyyyyyyyyyyyvh69%9@&AI+?G*xm[yyyyyyyyyyyyyyytidaImgcxyyyyyyyyyyyyrCE/=E(BJ,CK,DL-CK,AH+;B'BJ+CK,DL-DL-BJ,E)TS;B'BJ,DL-DL-DL-CK,?G*h`Myyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|qida*eb_;syyyyyyyyyyyv8;&>E)CK,DL-DL-DL-CK,=D(ueyyyyyyyyyyyyyyyyyvvfyn[|o^pyyyyyyyyyyyyyyyyyvjeaYgc`cwyyyyyyyyyyyuf4:"@H*CK,DL-DL-DL-BJ,?D*vyyyyyyyyyyyyyyyyqBF-IN2f`K\XCDJ.vlXyyyyyyyyyyyyyyyyxngclfbyyyyyyyyyyyyqeV7=$AI+DL-DL-DL-DL-BJ+MN6yyyyyyyyyyyyyyyyy~p8=&tdyyfaLJR2}nyyyyyyyyyyyyyyyyskepidyyyyyyyyyyyybZJ8?%BJ+DL-DL-DL-DL-AI+YWAyyyyyyyyyyyyyyyyyx~pyyy}p`JS1yo[yyyyyyyyyyyyyyyyvmgtlfyyyyyyyyyyyyZTC9@&BJ,DL-DL-DL-DL-AH+b]Hyyyyyyyyyyyyyyyyyyyyyy}q_LU2wnYyyyyyyyyyyyyyyyyzohwmgyyyyyyyyyyyyVQ@9@&BJ,DL-DL-DL-DL-@H*gaMyyyyyyyyyyyyyyyyyyxwyu\\BJR1zkyyyyyyyyyyyyyyyy|qjwmgyyyyyyyyyyyyWRA9@&BJ,DL-DL-DL-DL-@H*ibNyyyyyyyyyyyyyyyyyyhaNGM1PT8GL0DJ-pgTyyyyyyyyyyyyyyyyy|qitkfyyyyyyyyyyyy^WF8>%BI+DL-DL-DL-DL-AH+g`Myyyyyyyyyyyyyyyyyya\H`^Fue~q`~pyyyyyyyyyyyyyyyyyyyohpidyyyyyyyyyyyyi_P6<#AI+DL-DL-DL-DL-AI+a\Gyyyyyyyyyyyyyyyyyyb]I`^Fyyyyyyyyyyyyyyyyyyyyywmglfbxyyyyyyyyyyyzl]39!@G*CK,DL-DL-DL-BJ+XV?yyyyyyyyyyyyyyyyyyd_Ka_Fyyyyyyyyyyyyyyyyyyyyyskegc`_wyyyyyyyyyyy}o06 =E)CK,DL-DL-DL-BJ,JM4yyyyyyyyyyyyyyyyyye^KKQ4Z\?VY=UX;QT9tyyyyyyyyyyyyyyyxmgb}eb`6~ryyyyyyyyyyyy@@.:A&BJ,DL-DL-DL-CK,?E*syyyyyyyyyyyyyyyyyqfVRQ;WW?YX@[YBAG,|nyyyyyyyyyyyyyyyuidaTeb` vmyyyyyyyyyyyycZJ5;#@H*CK,DL-DL-CK,?G*~q_yyyyyyyyyyyyyyyyyyyyyy{mwyyyyyyyyyyyyyyy{pida$xnhʜyyyyyyyyyyyy{m05 6<#@H*CK,DL-DL-CK,BH,syyyyyyyyyyyyyyyyyxj48#DE0xyyyyyyyyyyyyyyyyysjefb`<~syyyyyyyyyyyy{m26":A&BI+CK,DL-CK,@H*ldPyyyyyyyyyyyyyyyyye]L8?%@D+yyyyyyyyyyyyyyyyyvjeaYfb`|qjᜇyyyyyyyyyyyyyi^O17!=D(BJ,CK,DL-CK,CH-~oyyyyyyyyyyyyyyyv?A+=D(KM5yyyyyyyyyyyyyyyyyumjeblfbxyyyyyyyyyyyyxNJ94:">E)BJ,CK,CK,AI+TTF)BJ,@H+b]Iyyyyyyyyyyyyyyyyvmghdagc`0{pyyyyyyyyyyyyyyv^VF05 7>%>E)AI+BJ,AI+HL2wlZvyyyyyysjbO=C)?G*BJ,CK,@H*ibNyyyyyyyyyyyyyyysjebKnhcxyyyyyyyyyyyyyyyrcCB017 8>%=D(@G*AH+?F*EI/_[FrhUym\wlZmdQXV@?D*>E)AI+BJ+BJ+AH+E)>E)=D(;B'8?%5<#27!kaQyyyyyyyyyyyyyyzolfb/jeahuyyyyyyyyyyyyyyyyyy}oodUSN=<>+16!17!39"4:"4;"4:"39"39"38"@A-RNE)E)GK1_[Eym[txyyxyyxyyxvlfbYZWVskћxxxxxxxxxxx{lVT>=D(AI+BJ,@H*:@&GH3`ZHkaPg_NVR?:=(8?%?G*BJ+@H+]ZDxxxxxxxxxxxvmda_ ida\wyxyyxyyyxxvjZ>C*AH+CK,CK,>F)IJ4sdxxyyxuh^N5:$>E)BJ,BH-vyyxyyxyyxyxngcsRPOskϛxxxxxxxxxxpeTB+AI+CK,DL-CK,?E+}nyxyyxyyxyyxxNK9;B'`\FyyxyyxyyxyyxyohPNLumxxxxxxxxx_YG=E(CK,CK,CK,AH+f_Kxxxxxxxxxxxxxtf17!ON9xxxxxxxxxxxxyob_]b_^&txyyxyyxys:?'AI+DL-CK,CK,>E)}nyyxyyxyyxyyxyx\UEmbSyyxyyxyyxyyxvgc`;gc`ZxxyyxyyxythX:A&CK,DL-CK,CJ,PQ9xyyxyyxyyxyyxywvxyyxyyxyyxyyxxohcqqidxxxxxxxxxYTA>E)CK,CK,CK,AI+haMxxxxxxxxxxxxtTS(AI+CK,DL-CK,?G*vfxyyxyyxyyxyyxyyyqiUX[>yxyyxyyxyyxytlрskěxxxxxxxxx6;$AI+CK,CK,CK,?F){lxxxxxxxxxxxxxxxw]]CcaHxxxxxxxxxxxxvmڀskœyxyyxyyxy6;%AI+CK,DL-CK,?F)}mxyyxyyxyyxyyx]ZDQT9IM3WW>~oyxyyxyyxyyxyvm|qjyxyyxyyxy>@+@G*CK,DL-CK,?G*zjxyyxyyxyyxyyxZYAxgwxyyxyyxyyxyyxytlxnhxxxxxxxxxKI6>E)CK,CK,CK,@H*tcxxxxxxxxxxxxx\ZCwexxxxxxxxxxxxxxx~rjqidyxyyxyyxy`XH;B'CK,DL-CK,AI+sjWxyyxyyxyyxyyx\XCPU8RV9PU8ogRyxyyxyyxyyxyxnggc`Vxxyyxyyxypa5;#BJ+DL-CK,CJ,_\Fxyyxyyxyyxyyxvg}p_ra|p_ujYyxyyxyyxyyxxngclc`^!sxxxxxxxxw=>+>E)CK,CK,CK,GK0wxxxxxxxxxxxxxwwxxxxxxxxxxxxxugc`6PNLulڛxyyxyyxyymbS6=$BI+CK,CK-AI+}p^yyxyyxyyxyyxyVQ??B+xyyxyyxyyxyyxxnc_]rjexyyxyyxyywBB/39":A'?G*@H*EJ/d_Jzn]tc}p_ldQMN6>E)BI+BJ,@G*^ZExyyxyyxyyxytlda^hca8sxxxxxxxxxxxxwiZTC9<'5;#9@&;B'mfbfxyyyyyyyrrhUYW@HL2=C);B'=C)KN4_\EwmZqyyyyyyxskeuGED}r꜇yyyyyywibNAG,BJ+?F)PP:pfU{m^qfUPN::A&BI+NP7yyyyyyysTQPqjexxyyyyyuSRSPO5wyyyyy}o_>E)CK,CK,QR9xyyyyyyyw@B-lcQyyyyyyyxb^[DqjevyyyyyyPO:BJ,DL-BJ+ym\yyyyyyyyypbvhyyyyyyyywng}rjyyyyys:A'CK,DL-@G*syyyyyyywjcOmeRzo\yyyyyyyyumwnǜyyyyysdE)DL-DL-RS:yyyyyyyyyxx\]A}myyyyyyy{qyo՜yyyyy{m^=D(DL-DL-TU@H+DL-BJ+vfyyyyyyyyywvxyyyyyyywmgSQO1wyyyyyug8?%CK,CK,a]Gyyyyyyyyyj<@)xyyyyyyyxb][?432yoٜyyyyyyaYHpidqxyyyyywUQ?;B'BJ,IN2yiyyyyyzn]?F*PR8yyyyyyyyvmgGED |q朇yyyyyyxkaP:?'>E)BI,e`Jtczjra_[E@G*BJ,TT'7>%8?%8?%8?%BE.TR=rgWyyyyyyxrjek|qjyyyyyyyyyyw~ozk|nuyyyyyyyyytkB@?LJI wn̜yyyyyyyyyyyyyyyyyyyyyyyoVSQSPOwn̜yyyyyyyyyyyyyyyyyyyyyo\XVMKI |qjwyyyyyyyyyyyyyyyyxskVSQngc\|q曆xyyyyyyyyyyyyx}rqide@>=KIG rjeqyoٙwyyyyyyyywzptkfxROM976YVT1rkesskwnƉzpՉzpֆxnsktkfv]YW5=;:??(0 ` 643c\Xetl{pԏ~r~r{pՁtlg_[i:87976xnguxyxyxxxyu{pi?=<[VSC~s眇yxxyxyxxxyxxtb\XKe_[Yvyyyyyyyyyyyyyywld_bZVRBvxxyxxyxyxxxyxxyxwb]YK754~s圇yxxyxuym[c^HVU=QQ:YW@lePudwxyxxtA?=vmfyyyyywlZFK/AI+QQ:xl[sdqfUEG0AH+ukXyyyyy|qj310txyxxpgTAH+CK,_[FxyxxsGI2][Cxyxxyu=;:^XT]xxyxyj@G+CK,JN3vxyxxxsdGI2xyxxyxjc^h}rjyyyy_ZFCK,CK,nfRyyyyyyxqbyyyyyyulyoʛxxyxGJ1CK,BJ,wgyxyxxve_KleP|kyxxyx{qՍ|rߜyyyy=C)DL-AI+qyyyyyyxtb}r^yyyyy~r|qޛxxyx=C)DL-AI+ryxyxxxd_IulXvyxxyx~ryoțxxyxIJ3CK,BJ,{kyxyxxxc_IvnYoyxxyx{q}rjxxyxc\IBI+CK,wmYyxyxxx|m{lqyxxyxul^XTZxyyy}o=C)CK,TU;xyyyyypfTneSyyyyyyjb^e311 txyxxuiYZVS;vxxyxxyxyxxxyxxyxvb\XCe^ZPvxyxxyxyxxxyxxyvkd_Y[VS;}rᜇyyyyyyyyyyyy~sa\XB976 wmgtxyxyxxxytzoh?=<654 b[WZ~skzpɍ}rލ}r߉zpske^Z]:87(  @41/%mc\{q͒t풀t|qpe^742'oe^wyyyyyywsiariyyyyyyyyyyulnd^yyywsbogSkdOxn[~oyyytib1/."wyyxhLO4NP6~q`xig_LBH-vyyw964'j`ZyyrGL0FL/pyyy`ZGxhyyyrh`zpǜyyofSCK,d`Iyyyx}p_}myyy|qΑt眇yy^ZDDL-ulXyyyxqvnXyyytt朇yy^ZECK,wmYyyyyphTqyyytzpŜyyqgUCK,idMyyyy{p]xgyyy|qj`ZyytFJ0JO3tyyy[WBxyyyrg`1/.wyyyjIK3UU v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = 'Cecilia5.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. html_domain_indices = False # If false, no index is generated. html_use_index = False # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Cecilia5doc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Cecilia.tex', u'Cecilia Documentation', u'Olivier Bélanger, Julie Delisle, Jean Piché', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. latex_domain_indices = False # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'cecilia', u'Cecilia Documentation', [u'Olivier Bélanger, Julie Delisle, Jean Piché'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Cecilia', u'Cecilia Documentation', u'Olivier Bélanger, Julie Delisle, Jean Piché', 'Cecilia', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. texinfo_domain_indices = False # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' html_add_permalinks = None html_show_sourcelink = False cecilia5-5.4.1/doc-en/source/images/000077500000000000000000000000001372272363700171255ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/images/Cecilia5_96.png000066400000000000000000000153721372272363700215770ustar00rootroot00000000000000PNG  IHDR``w8bKGD pHYs  ~tIME9)riIDATx}i9޳KsI#tF 5,6v4$nRl*-Tq7AcFHƻ,+B50WݣF#ݒf~&8===߶nFMt2͠\-H  @+PHRIߛVs9?wވCn&?úݰ&Êfh5rx'e@&u2_ص~&o@GHo7uzn> hf3׳;>Vc|w_kU_`f?i٦{nORJgIrr)8n=Bk2lVF8+J*hGfή; ,>lwOף-ܣV w˥1:!FLJ09= )(a r5P*!T $_uF4ַa Za6e`QUa!޾c? m[[meډ5B8אJNcx,&ajf |`k`DAc! mb@T !$`":(v^-!Qw]O?ܕ_ =7gum1QuSʐͥ;obt|PibKD/R Hf1Xbcmԋumi׎G~ ̢pS[?O;abU%:8:xg?B*4Àfi @3 f!ڄxhV0() ]@ 6g`i1(%M4 Z@muȞ˞GwA?ǚn]W-%>8ı0m\APEs(AA{R4vPy` (F&19;yH!Qe{vZTO0}x.3uv}?岜v5F*n";}f80t s2:ho~KW>P*H BL3x :ZW iY_s)s)U!ЭKM%G~pKzyP'+ Y^L̎Bmi䗚Tɕ`ݲMغ.蚁k5PBıc ϡH 7-E$\=e\c8Ă#Ը-^B($^{H0#ah0@(֯z]ZJI>E(<ǁJʲ_Ս D8Vo}nrY`mm~D3CҰ8~d)aap&+6ߋ^7l;o2$[j[TPp\`Eu9NݎA W(!\KXO/ >35UEQ) Ꭽs\H_@)B ]I%43|l)fU?_SdiJJ>w硽i:VW%۪DsSښw](E'Rđe?Aeq_V?::S,@JG غ᮪c\ H@ A&jL4ۖiN*];k] *CGa$e흥WJ)- H_JP@յr9 {CѦ%(e^O4c Cx mUkM3,t- lr +pfFit ! p5<.pV6F!|SP5a|u-;~`\2N 5~ʎ44VJR83xf+&ϡ֦Hazto3]7kֵ 38۷VĮg^ B!d ף#>ᚩV/~ ԰kXgfzl z){\US3h)7Wd}#>ЭH:ffh5*yJΜ=i`/(QeoiI<9tgA$N# GO;BaI*P0bL79e+CçY R*\G,R_U'>!\ c`E˺l +Uu !`\̰8aNDW3!ӣp}62WdZkgah5ö")}H aۑ*{EiN^G\bl<(gj_֔jJ) ,/ꮍLPUF4BqJxET UJJh(V[dFBI4 e?)MN2*HZu bF)!d A}Ϯ@*ԝF"cR%(e,$A4rbRxt"좔:jATQ8vѺ&Sځx溤gwI "/CS,l+ u `|kVnzjDsJ->Kڍ*P_߄~/csZtHY7:֖;#q*FBN]rR) !|4M5w*, j^TA+V R znQJA,nPL1SM/OT燐Xtw!P w$\W] 8vI)@%)kPM3q ԟ9 By.~u:2ϧPtsJ"W_ (P)Ř^EP*h\ժR$35ّxaHuqYx҃t},k]UY  jJߛB+*%{A+U>0djX1=3#`c-P= PR~ȕ%J+RJtBYSfBR)D"U}iBvށT~| 9|σiXd:[cIc'~~H ϗgJ\gDZQxk ) B('>Q嘀2]!~A^3Fk(IS=pRFG)[yxU #c֤/|Rh\0t Ԯ)@Sw / >)XֹT-Z"Wb65}CbL&ӧbxlv) ;SE?y,vhb&?L`@HSԶ8#C?=96m~{G(tR0i::W@U R ܚJ 4yN>;@3LS*)еt L݆!1LΎb&9Qs Ǐ VO(o5JBrAaؼN8 fH*ǡoZ)g0l spMeb\׎\';y s*զ--֡m%bW4/3Y'#V=I/^t0M)홰+c\rR8;@0K;w}b )塨u0>yjP@3IMB39'Zd?sE (ko9dE0,ܶ H``฻~kqbR nat|(x.-)L,FG?Dc}K ?>4PJN'ot~f( 2<܀x/9<&s/p͒Xu+#wZ*j5"PPR2y-áStPι!0BܔfpmŽMNd3Sgþ/ ΟȪg׮^jƂ,i \ &i!WJ!!p LL.XS0ms[2 9:&z Dg(GQlX}[:!J]ĀM6!H`_JC]k \ϝke,/( JLe:ę0=3|> J `>$ĩwq(&0`P!XgC:ذb ꪴٱ(ƿ2pԥ+>B6%+ٱfJ)Y;ygOkzy$I!!}{B(F)(RBH!$d)\ؼ}Cdy`޻ZӘ>};qcHò2jFҙY kLΎ—~пXiA_`+TpJ(H Y۔<6A *Иx+:ס!R5K}0ڢ̓O>8ϮX|RU,bRTrRYxƒ"AB }N9/ *o*?3# pb8# 6> tHB̈?~S߾ʲ"|OckFՇ.~x)\T&lj 42 ы<-F(B(zE5!ҫEIGe(?wf~' ;[̹9}bOO/*ƀ=x4fers6J"9 |/No"cۿČܴ͌$$OO=́wnk.bǜ藦^s l)1+b{''bkf={ۋ/y~3 (&wzbv2.k<|<Ҷ}v+rqz4 L9ܪmܩ[Q^>dgF:_^}ŀw)8rp7t;5znJLd#?D{볯\\Eߞ=W\f *MXoS%ԇGQLϩĻH~VacawwP}hf+PZƢ~K"ώKYY .e®D41ÎUeEH͛[B? 77 FVSq|(CROO(=O}oin.5&h "kn2б 鑯}^ذaDE2|D#{ }O=D;mN;nGWz0M_DeDd\}sI8X1;\1;w3vE |}JXKT3ÿGw]p] ;:g 1 n1|rQ)ܿvNzvs?ՂƀĎm 7{5+kBua:J0uvPE ;t6pINs o8;}u3B[u3zn2n j(ӂMS}PR@ wKސ﹧bWH3o}Ф9}R&8#u&cIaQ/!.#@'!diHFO)R3ƔRJA)Q9;E.g-^W|W biAA ,syW R5`_NמR +eү_  ܥhΪW#&Pk#Fzqr>)AtfF&I%t֭GkÀG)ٶM{wjƿ/ -p%|  @}駷Piʧ>Go  8ϞW_j,kW&?ݽAMX^:ziT"_ըנAGZ F?}WA_W(CR R,[K Btv/k @)ŲA  !=VVWt"]b!AAhCzizutA ;Q #( Bw P(  퇦iM~^ bAA  AAD  @A!&hGl"A: P,)EibY&: un? p8bZAuMrzeq #C>gP(Lell۾v8JݨO " "@A j&=wɉdI4R6s۲lm [)@Gi:Jw@hvškVXV(- ,celӤX.c*%ЯhӉ?$IW2z茢r nrD| FqeA!o`ZiaY6(u8rWp:q86kʥ"|R>Y4(s  9qή^WdʵAD,M0gH%%Lem @o0kC)p\8].E*) 3IJB\1I2&v,C{vD\QJRAD)t N1z4Sq"eƲm4/'؉m͙p=˲) r%#E!#k\%Hgֱrz S hqʥ"F93|(XITi())JY\>1x~6>}W# g'1z F6M\lN?p@E8].e(Sq ]v~ツ;:` ".9qbeBsw-:x<^q3~BiDwz"!,٩KdifiV^Ϧ!$K "n7F6É#9she+ (Yd2IN; ]{{@ADzʥ"sW+0^q@&"Dbzvpoep:rXAŲ,^ȱC26JP=: :H @  MOc$Ǹr2_wXBMv\U8gzj~)1 K%by`߽tvߦ躃~]k)Z؅KbAD,rGOge6K`H߽ ryogbۖG%Ò\8'$W H/[Gr x<"&A]Ego; Jq eKڻ 20 8~h/Ol)kd_%:zP8uO n,N8̉#): "R'2p?w!1 "4Ns`9gpPT>YG"=FC{w. $>Y#MGt9J ˢkDzpx3Y N`MI4W_턻W\>.]䝷^\*QAX%~#F{]w'*v.'՘ # ݉eYbAD,gNcM?:OSX4^jGxEɉ?\>s`G|¢Gp{ڿtjZ"ۅY.s`[$SIBn;F 2q2ᄑr$FAƲLx񋣘N0:k) w Ak >vۖ|AZ`j4zcC,w%۶IMǹzl|>G-+.4]wtyB|AN璶׵#~cGo Ap+0 ˺J&tq+Wc r,R*EQ9uU31=KU&kd4 Å! ꢧg9=%aPb6A&3š}_NTADoUlP%re|sJu)n/B"j5esAS 91.Xc^P ² \lO`j,Õ>yl Rϩ8w%]!N0r8^ۋDu|x ;zkwې1mm &W22zg(V,_C_*\nO. _G?Fbճe"A{Lzt2#Ћ?б?|`)Na:'@i:(EH6T$I&7NQ׹Q0-dZgWQHe9rb{^4MNr[PBQ )ҩi_'q8]2 f9u0+XJ'ٷOůp;\V>L%'8-v.L$Imۇ42FS#G8{>#Mudj<񋌞;Úe7QxhO>)1 38~h7 kͤLS;d&A$ԅPYRngdZTkm6ضCo׃'K1=cCX.uCdžb "]L&&'&Fy| a6s?OޝU}^\X"n!$8|`38xxeT.2Gf+5L>l;{^G?Ԝj+V(ll[U*l[;z &''83|<&]I^~%bc<'?K45GK%^>~\$;d#ۚEC7eX #g0|a:BtFzC #E:3M2 I`Y^w(W7:j*l\zw5G?9}06>?Q1{Z~׸o'_x` bc}[.&NpC|J;v+1.7+ͬ7:#q3}ꚡ~hGYT. tvF]} ;*Qzg&#N0r8n/T (܃j4@MZ|A|;0 #Ñy'ksk/֛_'kbT d˦Bpg8Νl#ڸ(;vK\"W4*_ hpr&66}׷^^tu6nfr,NSD;z0r\-0Wiri*T AROr0m~p$*#"0m"y<6db_{ k7 mss;ܶ˲8rN 19}Y-uE D--tzssa:zRYǾHQhׄM5RFHgO>+H>C|`GeYd`6$ aX+9]?9б!>1\uF |Oz#''CǷ>O<%").`с. O^A4*5+jQ @!MuX(v{ټ1֬t:Ҕ25n7ΞvuY 夯)6>,vƭKoΜGNt5l0$o~͆>yݮ};:v(DT!pvh]%x~kR s! + dX`qu6ɥ3_k%ljá;{l/.~?ȇ8O̅c]^܊ Au_)Uh}>?YG2r8?A}-}]v+ko({ eD#] օr93<>0S ܏wS%5(jvss[k볙FNa6@\.[/se"|*4^]f1i 9Te¡;o>_t͛>m=jjF@3"UĨ=PJlZ>AӢ3ܜ#1tlgذ9Iz|_ Y }xǷ<^Kh'?~kx=^r\=""H9ul.}ڬmY.[?&7 u߼_qIMzhdzm2+^b-{=j$(muar$ΜHϼOع;5V=WP^7߰^]3Ͻ=GN&( ?;ix:{۪LVLO1?i3~:6tz,_|tޅ sZ0Cvqys3"fεF9!eqȆ5{bK:[qr_׮܃ԝ`ڴ^5Qa+._$J`{Y#=kVIl5!И@i31 wͥٺw18x܌Cwbە3KmX#(RqeZ,v&9n+7F]vNhuG[ֶm dʅaMCӵz_?P}YXA]&PI`lpvviR* p:=a HD#]uLJ]v69w24>?k aGEX5!~3rܖ_:_2J%ع3X%"[lyyN_˂߾ jY_#}kw k_O>mݿ*6gh=J&{vZwCo7eca,[6rٲq,Ķ,lji@9ZgdZ,3c1nuF}>{jdx^Gm}uEfs^}{"ܪ$.6F)M\Ιf7-ήw~$N4%gM㯵­4wW`lnI \O&*Յ@I(pl3GeZDꗿF4e_xbnBG՗~|;ϰkNGNkN߬;C+{yכfmá<պη+BkF=Q{|ɖMtBΣ?up89Ir`8o6yZ IDATN*3TjeNmּjkZS߼3GO,#oi[|!Μ?^]UcBضahO %`s-`'8].'yevF IroϽ}Jִ]OLrq{};oP.IeR@*Z %t[f9AXT@X`j*ev?15++5oLoi@Ì̈C>O-w==A Sɉ *En_1AbjrQI\H‰#lf%5Zwsf`B{ڠBb!נ)m@CS ] Bm}ض#ajHwnvY>X^_@ٲqx-'fɆc?`XH0%n p{2|pz"},~cm5d4*#"䇯񔜋~`e;<];]7UذLmcc\KK+4UekOL2<2_2⍾B2߆@FX=k%V]x[ˢo4| S)to53 >H&jﯚ6oZg6U@21o޽?g1BP RuAr?y_q:\%0QCl<t8Y3یضu5aA<1?}klXQε-#g_ Ny+3+SGyg.6PoC?۹O>kncclXs/SGٵ'[6ns/~#pw?V}n #'.xy1w.5Q02:o#:7r,*x53G:L^mN>WLq@G0Cs\5RB!R ۋ~]K +#[pyg-ҩif|7y|e>Kk ;Ƿ>ضu5#HWY=)o^>zcĶ}y{tEǸa*|]뽟{֯mql|D-(v|ՍD#]m#?is5GME #,ܞWc 35fg UM,[໻WczOU]"QLCtbo @X̷ܾ if2*50tl״ݓO[DLůR.[(I;w/D!UhgB_4ptI\ilZ>њ|RRx<)4e_P+El<`(W'7䓟bI5>oJr=jm~_'5Ŋ3O˟?s/~ k6#%a3Zlwa%8,IJ+-5dY3Xg79Ǯ~4M["]FMztE5%S6ol[}۶HNOT塍[*aVSVб!G|_J]wmOy,7$"F[n'#g+/'O'>˓O?/1\AB-ΦX*r?]G3eg4(AkmkvXֹ6l] ?&P% 1\%&6GL즘 \_gwr8+W~7d0>)|n bl766{_a(F>@{=]wt܂~74BȮHt^-ޯz#ߐE ll%K(+!(M??)|rLw ]8Rwb3B۶J;i۲x};L[*`>k7Y7V$9W~=/h뚎zj;ο'eQ@X4m-Nƿ1[_kk9b9Ow ]3(6O|˽$Z";l"/MkNڸE $ˢeJEL -I*=igfmTא?S0O =( Run`.7Q*ednz6}jdnlly>/;5hl.i(k iN";е%\5_GKX躎Ʋ,SF(ذfڿCtElXso=^Y̲R۾T,+oZԵ翍| e- в![@1J@SF(ٲi˜zAkfB b!ß7Ff9J+k2`ET_RMdt=@P[L&hXii:p- %FPH" EnE,J$9op37:ͷNՐR ,391$ EDq=mov.AZ^Xm)h T:1SPrIM=\uSI^#iT[r>=5z.AŢycӬϵ  k]K- jHz,Ye:x Z>\8nO^}ɫc)L"{E\,577ۏMkK- ޮNmfLa.xrda6FV>S" Ls䭍`pjo %&n>gs2Őj@c׿6E"jSF'AC b, \(ռ*"<B~|&NS2yzt9C,p- f2δz֜93ףܢ齲j}^._XrCS:5́Co,:(Np!V}"]i (Zs`5io̾Ro*`?˼ֿvxȔ3ugYLrmsm;nAn|f6nEȲ1lN[6o#Nyrꥶ9Z+v4 tjh{bNh6fmb Wdjr)ZXAncەL]0ؔ 樄SeH+tttr(avwoGP'Kdي5D:GrDЦ@)Yʵ:qeR޶;o.;_Ps" _ȁmfh**W2PN +ihfl9[|ɡ鸕i+ $# E]B([aZs `zL=#AmᶹuPvevn?Y,p {;phYm%[P66Ag;_7?MvpDF&A+v4K-'Iԝ=n?$0wl}P 299No@[_|mʺ3zgxun2`CT I#:޶Y*94:Ah+o/6-~_E"GEP8WJX04ٶPoͬ]l:I"1T*Y&ꤻ{9PG[gۖmqn::Ef t %J.w:vL';mcjDyu=P®(  0`ympp m@WPȷf"՛z3ͫv-+ZSNP9 \KEi´G ,QQJQ.[`}yJ;HȶyrY )'.tM7R-kxܾjL P 4l\s,e#'(sLǘNƙNN2L"9emyޥ4v# v/Ci?H:ġ;)`תly`,PQ$Rɉ%q1:B) ˚MHN29u=@!4zVȈ$B{ +qhD@˴[p(=>: hP go77Ty9/dbtvT4UCOjx۶) t]1rH )kAF۪FF1P@m5 fb2L/G%4K5\޶;oȠ)  DGgH uSL:=nɀB6k}T@U>O᳇Bp8~MՖR.v]L~Ah? S̵M$¡;Qu~U@;̤95rl.S QsXe5/<2 4#څF:޲TJ$Nuy{8f- 0Dbd{ef}Oc⟦5%ZEwO{u˥&ѕNW2F , rz"}=hXM}fZ&aɀ(eNʥR[\|WGrd:QfI-1CW^wD ,-0\nv1GPhiCw#n:z+!/$ye/tc?`U߸/aEd$ai ?@gM0S-mH#[~L6Ŷ戀ݰpm9C7 4EHr6v-mÇsL&.ϙzGD F8Fғ8tY'# KKZ ɖ7fgtg`a S`Cl{8tj%픚bρ+$j_-_\nBH|,ӢF GF!AXv,f/?A"ˈ{C-̊4TfJgә)./rfRt5RvMߍcIDAT+͒S'3(Hgd Tp5p`e:ؖոа`7 Z0'R,ػy-mɩ͎a<7CZ޿}@ Cgʵmű "n=%#Rl!<^m|?P(YI4o'\INF0{vjTe #޻l|N>q-f9b `ېK_Fl~4MH-V`,MOW6  Fh#+XvmRH0:K44 foԀm7+-k3UN'}-~iei)$YѪ 9O-}hJl <`5'fB+Vcz=r=_T* 4.kzT`V#hfTvYb*9O?{o6 o&̬7;B- ݝZoZdc8:kG: `6m`Rmh6]}|䃿@$š9 Y ͂f' 6@)LT*nsacېIOƛ//P*)Z%ĬY|L֮y6*f C: u (#d6Q>O3tm |?lU9Jx}#!6z᳇0r6ޝƕޭ^,"EJJԮԭ Sy_𳟍$/y`cI&+zt[[j픸dYۭ.܊5ӭ`A,RιCeoomnQ:;~@+dpDJuersw);e*2XP糯Nx;Q44M{[NV@ӡPWht>::{SyKZ&jvbim Mk:M% }.ֻ'G6>'i c'G!I$k8k' _4x>RsHiK=m [/Զ:'CRPo֨5(a4E)+k!aJ Zw'ZnN"ͯG o& iFjc][>m7/_v#t{IyQ !$<i2}- kYJ"p+3}mLӒ!$(ts#p8x,Jd2ť(B /Z ! ')IkZq\P"𲜚>ϱtI%Jm6){x4FYla-'( %\'G<HFTB މѓ̜HآQzL,w\R+.n.@O'?`| cfCNN))-əLL!$73} ?rag!Scdb3u]j#vo](=VHK}=Ǚ>aHa+ qݟrbjR|-rQZ[|T=)޾9xJ!` \|5}*GZ\|ȿ{NLr)BA'),=rRaMn+ʅ9LCqbxBC<a>ar m "vHgdb`Y!)BAŹO1M7sIwc貨K:M~E ^_!.hjik@7䶮ו뺔rhL$BH8L,+ĩ7D򳿣X\%5h huh)'' =d}!+<G!щIB (HdׯQIdDrIV*P2To126%[ !$f.tauuMm}$3=h,?)P/.aa HqxIRho}A7 tŧhK$3#逩ks2pq8&DR#KK/`J3\?WqOVK"&0d~{Y%4֖w[Bw:KO?{S+.Ҭ扦'dm>8T KC8d{tsjk !i.._={op/ɭR^EEeIfNy1ihtvuqr džF1eK_! Œ/>5ܡVZ!WYN!4e%>N>K* 204EɴH!$8dF߳QǭI!Or]evrZ1ʫ`Y=.]=}R !hݽ7d}=zj!Fx<)^TԋnHwv11u0LyI !$;5͑ǸݥX(|K58W B=GJ)*Njaլ`Z&m?1Ro!$Sm}N>_wnP9oRDRGS>:" $i=DJ$^X<ŷ2vj7`-*"`Y!<=&N1KkX"޸S!WO#΅>`r"n7(4KTXD,|hߣ攩;9zC2H%;bh$v$*B S4gj"yn (dKTr&xHێֵR^Q+hV N<) bB C؎02qs:sw)4J+K+hF;A4N(ztrJA^ѬPn][&TG342A[{'.'Btp6 qurecjEtƴIhPm0JSmonK'tt14rޣd/aM'9~bzң9f# AmJ5Kð18XH$v&p]F͡Y+nTne#1;964c'6>!а(C'^x9ֳ+8M_kPR@lP3Ųv_>i4knoV :`:۶tt08H! 0LҙҙF'NPVX]^$ZrHQu}Bj TR fhk(MG4@G7f6)|?@) QJk:i ]P!j#GwQl; ! ~H4ƱQ n[^# SZqU+4u||GFGxT{Fl.7t ] i:FGƉDcē);ɴwbGh. %ċ# 70fSVPp&y{J)t]0L4}cV mH9g BH ΄Ig:B!v!B!@!B!@!BqR s:B!!\ B!aT7co jcZ@Q.BB!!Tȯ`{ܿʒTH!8ֳ+[lnY B!ġغ `iaN*$B2FVPU@)hk<K*%B"swik[ u[OPJo>&|BqH|ǻnR Rba7j !?)[GU? O?ZujB!V*_?99pok#?@iZyR=!\s4 6e?hv60]?shiՊA~m~G`v p SxHO/Bxε~k?~4;۾{w?I Hll HB!^~o~_u?\.'RIENDB`cecilia5-5.4.1/doc-en/source/images/Grapher.png000066400000000000000000001602211372272363700212250ustar00rootroot00000000000000PNG  IHDR sBITOtEXtSoftwaregnome-screenshot> IDATxg@S'  @! A@XUAVoFmZGAY7V{W;@!-̄8/577=Br  w =ɋ/x@ 6H) }A~ƇHjࢴׯ_{yyy͚ {2b2 x;/TiȢ77͐Cv'eʤI/w7zϟz )~~.[2LPh)vBqm ОW6(C4bxSn?s=O~-[EZeK&Mx¼r = z"L箙O2_~+WUUUc EpC&]C *UMj*KK9shZPP[oellP(cbb233ю_qF[[شEs~׸dTŋY[[wD7xŋ>fjkjBϷwph)4D*K%ZnTRB [pDEd#Ow9*CաW{Oz<"d{_4LvbbbBnnnd?''ёB 1 6666mh/^OR#~툈zH,10pH& 6.Ӎ 5cƌK;v,==}ٲe[SM6yyy]v-99!믿ڮ]nܸ0O>>>ڿLLGHHm˗e˖+ܹs'..."""##Ν;7Eb={o߾Mє?^{qv9TFd2DB&>|8f̘5K$?;wߩ) CCC"xmݪÄ>n+Z¬.)j!ݨ΋秞i !(NEJYsP?cBY7|x<~/C/p(Zdr/ߑWSc쎽9O׈kykdH׼AS-r9BHEu9so߾>@KS(S={ BBB:zÇ]Zlĉ'Nl2еkZJ&,$!`&ŋijN={ÇO8jժ߹sg͉<<<&L'4OJ=駟6mڤVkB0;;{ĉ++8Ǻ˓T*!sXJ=zf5i~ɒ%+Wnjnn>zhL®YrUCL(jyKDcrh Ey{\raOCx k&!WU"q{I:zY4mn@{R֔567|QHՑu؞*HʵԊԶ/[O_1rD5(g ; iXI&{fggߺuTPdgg+feeu5k֠zj>QPPP|QN nocݺu r k޽j޼y֭۽{wG-pTO$-d L6)ItnJtfOGn߾qƀ/_'Nxڵx???D"!tرkת&TP(d2d2*88ӧuuuyG)2UhѢG(믋-JHHGnlog׮8۲gzG099;<;KKcՂ!vLQ4-w[zԅ=*"=?C!EY"jB#Ow>of*BȠx$~+T6s?cOp;B S &CQNi} wt½{1c… Ϟ=OB"!*p¢gv)doذEU*3녩P>466޴iӪUҔ>?W= ?D,WUU>U믿622255۴iƍwϟ/˝u4 BK( J6m S(?C]]w2}۷o8ɬzjׯ_s܉'XEr|ҤIoGܱ577!=wsf8ݼׅ 牘~86lBO~6#|*P [6$o_f>9Rf!]1iO!"AzӬ*_A5'}f}ذe/&CxJo111744D*KfZٽ{7 044ܺu+Xn]([+7nDܹC?9Ν;g̘oZ"P7i '΍7?>qnٲeT]`0:zD9'\xq_|rY2 " b`2b8''^,z!$ ?~VAAA-*HXUU"G=}f Fd򸸛 ! i4L9b#R3RYP2;"9Bd)aÔyy{y:gj0@HvǢwgxҌ# -riL93[ѭ\4ܩ+wMcS\͚~ ccLcƌQ/***-[ƆL&{yy͟BAAAʿVf͚:}tˇ-|!@49| zEֶsGIJJj9.VWWaÆL>]F222-/9tP3339rdaaa_JI$tLFTgnn^___PP`ggꚛڵkשS'TTTp8Ф$u?|plcƌ޻woqqq4K$ϝ!MWWWJ1_HdlDS+liBQW*jIƒ;!$4*c̖سWJRgwW> |LJ?-;k.T?<;Lw\Q~nQ'>SJ>oґi) ,lvkBH$[.))I$%''3k֬v?s;;'N|7 ,X`Aqq7|s ;;?X0B'BB#@S(55w09{[L0AwMlE"Tf3fȘ={v焇;wN1Lrss9r377755UyB}}rZd2rR+'Nx[&:d29..Nf 7z̘M7+zga oUSLNִc^4Y3lM;E. GKvo H.rÇ\\o@훨귍A U0)w~„D"ٷoKDDD'4>{ ___{{ &(ʔo{76+EbiHd!O6314۳gODDƍ/]gϞGu–-[~}ݻ7))`Ξ=D"o߾}ׯx3{k׮egg'%G@@?O9UUU񌌌tzN#Fdddt2?U] ::?믥R{w{BcCCsgbbw^Pܾ}MZU=h_hѱcڭ7mڴ|1K$++ÈRt̘1+VΝ;,Yɓw}իWj-lDcbZi?!&1aV/ɤ$K^ɠ[dO* \.(JLLLMBCC?ׯ_~]8( @蟚MBZKB."Ps^W^Ԕ~Ç++̩X,~׮]ʯgH$L^z+V455uTZZڱc:xVOM4I{ۡt(yccceee[:995544jll,((|ҥKϟUZlӤ܌y3SR͙ERWZ#۲d-(q"JJ}Ր!Cw9ӗ˗/_|6=@zT*d2y{jkk8̟4$N:UWWW_yyy߿?""w={ܽ{B"jaaqǏWۭU]]y~-+)dɓ'3oDèQB_~EYqQc—s-N*%s>8z܄Շ ]N]{)cHWFjШ5/S*//߸qc```ۏmM\zd^O !wދ/FFF*J Ba`j.j333 CLDq7)Tu!Vڎ0h ^Я_ mm46LZZ^WܭׯRt-H$KThhK:'HHL S( Z&,:?m/^P;222-xV@m\.YurR(A 6Hf`nnw = Bw =?B R(i:yu=k[7XSzE[PfMRZR)f+茶R Z[YO$&a(DU@Ϡ@ ^N&;PH&^N :zI˓;~V3NVQS',;9 C;S·Oj0Xn S(5`R(A 6H) PPj @m웑ם @^b: B!ޙ\/l1>(z?ϺYgVgVa )%+$'k>g[puF"A 6H) PPj @mB R(A 6H) PPj @mB R(A 6H) P[Jqq+X:bȈUU+09УH(!;&&>a lم>s zdY2&aid9bn(كlz.S(^E5!DTpe pvFFWSVg݀3pusU Ed[Jȅ(//Y-I! S{?hZ>g[puT΋(˯8a, s{,}P}Wï9)jƚ-;y;L[zxST }P$l:5r#>?rУ[NE /L-i젎7;ĎX\}Pu%!u))Fѹt Hz֩;E >C$vNE2 GqO7r]@"韯}8nfK \9c}g޳ T (:p1U;rح>~[?_Vk@7n} O-lnK6s8%_*Fl R(>ü&x %HB,|o3"t0m2 HzAP˲ykC(ԓ( [ +qF 3 V^?@[BGia$VmR[2- R(R߈_Bm /m5xk7YZDiVͨSքXp̵t @5znn}~o.h0/_ҍɠ];vs폫tWZ7> M¦CQA3F?u[@#Em_ G@ L*4zp\qܱ,;brn  dzCStvE0wLq⡫:( kN|:H[ɀ]^+A Щ񉏮<[e!Jitڒ oDǧ>N PN̽p2|f #ߞ\\H<笟q웓5|#\BtF۶V B >PDT X=P$G670 X5>NۢeRޱz] d*y܂Qw4Fw, ^z`ܑ݀˿ '4o O$aJg ^=Or#0ɟB%Gpx|=PmI}s{4: X:`.ۺىx1 hEaFQgߍ`1k'4Tw,R(GDY?XT2qCQ5BcB0$lmˉ a>üE=C& {dqHw,})K2,r[ C;&l,bwQ(xkB0P(N:C1.yX@ 0Fx%2Xz R(f/\" H ?t?'x_=mW^'&K^e!J;M3˷-QR(s/zy :ޱ`e\9sexGG@SeUō_<X0PiN @SGPl}> jOm0sԀ`\NӓJފ+: IDAT쯴}#G"o~9en'\tSrXzR(@7;b9qE:ށPn:Q4sT:"yN; G HxǢ 4:m.=Iw,)@m\z|>5Úb,ۺ˅ExPfxvE XtĞ[c R([笟qb >{N >U_#; JXp("jBX0/cӈС~|}#H*Ieǿ;"t(ޱorx]g ޱ)k 3t}rxޱ0gL~i8cR(@׮DK+lI E_P[&Oyzޱp) "##(z^vE7 X8~ c}ϧ~:W_# O$9|{:m-\ig'}˟E_;.@ hK2l)xÐ Hayw?;A [/tN1|>N;v[$_n'=d4: Xz*y?-(;A [aFCWm]`1gs𴛳~-тJch P n,Xza^‚rI؄w, k"&{ t;cDPۢeRޱ)}T,=(C񎥷yk$!3 X >MPtec19<Xz!"9ʛ11HӮDUjnI-ے tzφ- 'IS9d[ti1v\c`R('oD/߶ؘA;ޏ^ߞ( BWs~0 X 7i+Cn=Q_#; ϩ5cTO{c[l>.I)H[M"FN7ޱEcX+HC2yhgq"s77vX >܁KCSO##Ҟf<XGHLħTIFF.J?uDޱT˯}aF:USxbNddd=/3ݵbF>YÛy{۹M;zϭopuIʻp򲭋{z8xY?#r[t Xj ^_*rxZ5jCQM&cR(zᡈI!n.x7v(gm2 Xj ^K,|ox:3}!܁KxPPN ĎXc]  >SUrc R(z+qF 3 ޱQ%\z2B3uA @/Gia$ޱU1Xe[_8x9/X] &q_-ixqb/loNKxVݏ-ĭR(z.Ʋa 7"kxWC'7rs+P51sp=Czᓇ=X$;KQKE_Œz3ś{  ^It("*h(a^x459I$r q'_+)qlBNwxXF ?Rw 3$RxDX 8hlck ^5 iJ |~t!PPumECǨONк[J >C$_tb̠/߶Ft|,c@wjxqDX0mӬmu@WWnMJD4{ܔqMO|t- )Trge\9ΔW PTzI$s2nN_Y~[PʓKNs]`!Ca^3R{ //j1w,@[wcL&6vA  D(3$![w6^F۶VKo*?; 7;)r9ohI.]@ *0o+(d:t! V CP=^aiov'-x؀E#w,\zc|zT,=ށ`I*TdPϝFfrض,k*X涚4*=!ٞ'_?wWjQ3J*u\~bG,bxUVmnRcA!"yN;$g܍ j(,MK~}wʸUl#ؼb 34߿t뀶@ Sd_\ԆWQ˫Uvpn$ `0c-3M⋻60'j=Kۛ55v y5Dj-[+ߜ쿻F4 M5Zh ns_UVr J8!!D[wsS!#Z*lV`?2ؐpד_nÞۖqK HўW =NBZzw,##/i/? 62З~#|Z2 {v3E_А2,֖N\B.x{[YZЕnjsr#ُ BߝmZn)gBEﰶ>k4taYhkH3B59vq7[ S{s)(%ruA?Bqr!5tɛxǢJʳl$_j40Ԙٜ?!XvJDnYt?_Em\~5 !ır)t(}$^?t'rUb3_g\[֖>MP%oӋq1gGCryOBG\=v_Z9Db25u<oJ*l=B((I)פxVP)BȘA_mGg',D.񪊕˯lخN/]seÀNb:1s[: [˰:g e*ȖAGa}ɳke|b[R)(M$.exl@562CQȴO.w:aeQko-mˆǿ=Yc~ *L,K2<ݖ6e,GLvS(>C`aP%a\.?#7;H_LS~|'}s{T ^WVZct Z 8OlTG5yEɡ}.0AXud@ ѯ.=9e(ᴯNXI@yK7OPZ(ڻdSWvJbD@&Q#BH$* I.рLH"%M, ;+iAe}+F%f9q+4| 4kCpNB>)BuN.UVYu}Ə\ɏ{J@E_7f~қHmV+$@^.J7bgQz<ö`m2MzO|!*ŰgO_N[mOb2w8BѨtcewUMF겷xLF5DQ)FBHq+xŒgB"dɴ;b9Z)WQ\s9ձ*mY1okI$* &>P; }WY]fj40hq#ħCWj}wRa@-2:$'I^cIθ q<1>5<`{`"!-7i0Xw#tlEzb/+BcRZ )TデS_J1xڵNXū, 2^Kd2)bjcth([Y2U MBq<+l@JK|=FEP1L|4 z^@7֞{7F.qV*Țݧ|̼΁]SXN$Ri4z>IJꖃ3np{fFypk9/WþW˛M.~ʇ +(v7贊A[ &aHHvV\~6+Bi d*T\,[Y_/sېf\ʅqPQ٥[1X~ȉ %Y}3ggnC7雪rԸN{*MJk2m^:3kBXMQMB]/h ec 5ʻk5u@0sjBƫ-\ :"+Ph ա:jif΃KGDY?VU9 6^?F{s=rkNN:TX[Tv ^z4-_yQV0:+e2C];GnI4XX'd۴:nm̭,R(^ő5Irt\A!qpГ6\@3ܜf@ SچCQ‚՝£ DRhJ_Qg"ztcI꒷E |ˋyq`[3l P/ܛ[4OGW^"FMUC]g ,X6eCd2ĪOUW~q̈́һlÒi_Ar^eϫ$ $(M>T^nzL&&oP岜WnN5 `oY{iGzc RL&MͼUk%Y^R9˯,䜄[]!Xvz^ es 6|_9GO~'o[#B:raݓwФNXI"bE e`@X9wٮյ\L\A'zDW, Bqr:>9\qL3yקv9p/n?y[SE)7| Mu7%U%VoÎgDgJe;6Bg>O6u*m[oỿN^I&S=5%~cqlKvXUQjl)Lqw d+ WP#=IBm'4MKmpJW"s6Ff\QV,wy_d2)VvySe\K*K|C ^u*@q>P"Z$^9tʖ*--hv}ss/w8/li;^K+2PKͺ8۫޴B642dto^OIR(R C%!0?x"m%Wrq+*KJ:~eO^>ѕISnYHvgMMfswV΄npW"qksd盙\n5oV"qcf޳*g JՈRWi<]wpⷣݣB!Uwf:`ׯv%Q<%6˱{cy\]}aN_R([OP,7jNros{PR-]ihߔZN̍=~ƌ| D"ud̉lng.öʸ9/Nx!+CfWa O#$نZTI#"qcna3넕b٘ --t KO$8ϲ^qߞ\|M IDAT: Q#†zm\B,isq\5K3;_ג y뭎'DfL [f,֩`@H#SHteqyV#l2k)Lj#!햿Wr ˴M_R(wiCg?r2,e<WmBu+΁9ӻHz}ǂ;tnvR~MQV:~ufswAFq3|ܞ[7^ͳb9Dy4iRPo=OֲXl_FXfz5\*{܈L5:79zu!~kn4=PyP&X6QA?/˯$uPԬc6~q˔8\Qm}G\Gl#WPKtpfVs4/mPQtЕe[3XDV Y{`8Wmo۝(A6{w/eno/TaizBڭj}uCQMBN誢k6*O^r`4?jѹ?nEtOf9+U -\sB$;Gazաd2iZC_-Z[x 8 +J er ӡ[}4 <Eɻdv/ª2nNQn啔wg.y3cW+ܖ\Ʀ q?Oy=yy3Wǁ gȯ˵Xi_`B/ucS]\Kn+mX緯(5k\bOHތB6424Ry ~AOۥ_)BƧD9 ,sVb8VaF9^e i֖TYjylgɄᡈ a5TXnnj$\q<ʸ9k+X[ƫW'T, zHbMZ5ZbqjxX5).c:&N_=Bۯ=Ē:a%\V QM"'~bL7_pw`7.8fIIEVJ} dW#VL աt^ [Bz[99nvA!X涽`F9/z8-rكvA!b'I#:T˵xͨ#Sc6Hbs:b76j29Z󨺇H$1L,'ߨo25 >/v*Vp:L&}x5+;t2F2:o?mTqi&.ur<\륰w)ʥ>MyE^n['V=F9WPh]ZqsۧC%,ݛP(b:˲q}]:gk^RX^/cs`ӡ5Eai} ~ggYY7(B%/߶'/㱯\r%F /8%lZDhJc.rc:w'lu?{QǒirOPһ@ s>q@I:(ohKLMX\inK"zz LCũ%2W3wLj^wWY$qf,b2n.-TdUθHQT*>ugnsV哃Wd·pbzZ̔X,Y)6j*j+\/±r)ɽP{8j_?5YP-9 T}BXaj3yJfZvʸ@; RJ&ԗ۴סKLsWSK~wzc#33S:];U_MZQ(Jhqs 4*}WoRnh{(=qOW]ŽJ'bES(kSRu䌻>#;*2V"|7_T?Iz}eak'c+>Z9e> U56^62cyPG;;-LTqI?ndw\S𓽀@$ō{[Q먻gmmWiZm{:ꪣnmU(1  d dpCxqX}ю08*MN=ye^udhOngZe퇢*o u0 wIJ|ӂpH>uDv_( v&\[a6'03vBΘBPNWgy7i8P]( _n-j퇧t6AAc:, ŻHV? JEp|yUB#X($*lsEu\fEhuL3D[:hdPg@&\6pBx|s r_[,7ϴ54(s\^I321mˡPY ﳶ\ԟnn ez֌VA4gf^ZP:&o(Jhh08s ɦvA`;ђ=44֐I>f zCtXgk+j*ô!(D#ˡ4j;ԃa#feɂB)+翈G DejGo ;)bt_G Q<;Zͥ+m,0ɭg2yakɵȾ8,ѼOjUtXY3ig{{;wcVbCR#/-'MX[VVf 28; ,vWZ-wYLZC9]2kOrO"LY1vR\4? M8˱fB_Zr-?j@NsKB)&xe)O&=vdqʡyQ)KRSq @XjXuz@$nd ֣ȿLSh4/n,AB9)W(eo;o-kr^Hâ(wP)ʿ&$$$$pý_%'MJ^܃ V(T*aR kجqAN=|WjgͭnSn?<94sڳ,[{yAvoL2:\FEu ܊j 7U iq#rdAH%סXcz+* KX=EZ6З8Fjlj4bċjcЂ:{5pv 2QDF5#dfuG$P$ji+d7#XD M_i õu]>?#ߦz3_̛~X13%|.+3tfLx+YE1)Eڨ֨,u6Moߣ$3f%S.PsTvC=^<63&j~γU!*,ˡ*N|qt*GX֗s&}xs CPw3.*徼fd纴7揎J0%-J~Cl\ ՝%׉ m$RNNgPj2'v|O,O|ǬXMcZuEY,&vsKÞc9mmUMaX`kW-9}ۛ׿%H?ZytcrvcO n6*헶ʘ.E˲ @C0E59Mح05T56ƒ t?N^&MpIPNںr[)-i+^sʔrD(Cݳ3B8*1oy[QaY r~i#D*2f'2esyu UdС݌*.lxETo߈W :Bc-)z)Pʚ$u^deb" ;uG= _6gM`ϡmru>;0RGz?"2 5cIY]nTOO=Y-.wv۫_3n;LZmϳ58LcVob0i\`C`Xӥ%kgA\OT24#`ttwNs#zLkS Í:!AaaWMںvogM p['mMaɨf9/;0cAsU dsmRyٓ>C1'.mR)m"Om8_esO*<8 nhs<;o2T J}ʭa-It2e Qn؎'ےHPN7<?#ͭU*D(zP Hef&͹*x1ZdJdL"[@'UiYN{檩{6VGҊZxhJ ZyɅ/ob(ɟ+۩ka}%l´}>t>K?Q՛CdgK{ !40D:SFyYR}(/d"-C!QL||ML[6g[;-=$ejji45U58pjw{߽/{埮ÚO𜻹^{罥|aaۿPPoNsĘXGKQ m{'G4 &yjP(m6/0 ?yᠾFDv^"1s^LFZ]Ez3Is所?#VT޶Cڋx23?z˽-m{4]h`U*ŋ̥s5AlIX&X?/5? `|x'/7eG#'::^%Y޲Ex_ӿzX '~7]ITs{CcMQ'9ZxP='rdos7 ..jbA2k(̌yD?}aa-a}1}p#ZTV;QQM} @a}f*!Ȉ$IxՔ[&%\ô0+g1LGV\< w8r5O&)?jqYK9sur-/GQi[?" (ߺwF^fQ:y{{E DNBń6:TiG0L`D>ތ$|֋黟n\Hϋ o2j;Q=m?߿c檩ڥQ#j:C_A9z IDAT3qM+i &w~IA'*A ٸk )iHi}o“ٶMY9fvXE] bvk[`VT,)A!nqM-eͲ:EڤN!E Ţ5j#aXsޱW~HlĀ9vں=[Jٓ4_[o|Fם;Ķ2l纴S6*4ֻqX7%>oerQMg:[ð/(t:t_5%W#(E0R@~{LaU݅6FXO*juAF$_Ƙ)@T '/jEПVU1+|Gd. zg<6lݧG_t Uy,j7nգ1]JG0C{-Q[nqzr㢲R ̺"k~$n&K_&vܬ)a<evg 6#ѳp0NuꜛR?E^d7hV j [Z$ YGrlISO:a%#BǕ@=pjLS%onq=umvզg?ݠ_|~'&4{b46!d<< J3v8(7G~?F-WB".wXۻ<pfpL`ºrS^Z:}]7qd|7g)~{7|l[4LRLL4 e|gSՕPk_Q4ױas=Toib{uJ\.co*r]vQ }9QXW!WJ;Z`]ѨO] kxm?VxP@TeSV}{񯁽Gth53 'H.^ˡkQvz|c&} De4_ed&-BCWV;B(0NlM}YSX$DK@Ƽo:/NMy"Q&=Ш͒z6/ro "= | 5Q(e+ʟnjmVN^,ɷ*`0آ5i'54 ot(Zm'S'EUGzMv z֔|A t[WV9~z5νeS} i]"\gf`te"jZ7u_BBLzp^M/٪Z5R.ì!b"T;ݢ 6nZr8WfQPMf-@e=a': 1DzŰeFT 0Up҃[>^nPP~[rb?_wH6x(g;Q4Q}2.s] ^io<Vz<~"]T*IR7rƼ_l5YDAւ.x,';镑;ZbY3)$jQ>h'SqP6TV%Vȵ;Y݆XdT=* 14?+ bEGlO1%iWG4K ΪG %ejUar#/WwטDuY~yT?BDQiڸUg=ݩ~˦PQl^`eu;7߻q,14>3owpN47[ul͌xu#L[?ֽ*sEJ毛kzAP3LVdL~CZ50ѕƚUYTFpma^Z':[ςU K%C3WMbU@P(t'yaQ9v KLPhkK3hA#vJn](ɹ{KRSsuzY@58=萾3Lܲ$ʎ7)Ҧf L77Y8fr'+le j>d4DfM$r &ͭ =-X * H *u=frwE%Yt]rXQ2QӊMLYxGXnaV)-Ġ11Ὤ|c54H۽/(f1Bݦ)~AB-%Yq1N]b۲5’롵AdžK=VY^eޔ|7 j4jQ}_}ABΫCXkЅյŏ_MBݵŏ@ G'h9TЙLUkTRY~q AeniQBHhkf֔^=nBxaHbcB"Pf28j?/n׵&hWM[urZjz_AndkkDJʹ2Ĕ_{_Prpzl(>F}q\D{xχЇ`*|~32ƌ:;7}Dwǖ_X#Ku0 SH~^dF!ѴI ]T<*6iréׁ93lB!EC,V+ҚZs#ʭ=c#6'O'1~`Z juqAhuLu-"jK>,cK fL\eJߛgWXI}G0 +w lFg Q%-f'pޒ`VJe zqu}cM:F"mz33&w\.׋+W8[ڶe{{Cv8go olf80<{۫`-/t_ΘO\nM|10a}%-\TAv<b҂Ltʘ;RƿlDei/($` ̘ _-yR$ePTV7 %F'>/"<gHޟw(lBA,$oVrtN ~a]&fbp:LKmS뫇.壜n?0 *AK97EڨTRG>w}~aAqx 폭nj7)T+ArT4nw ʺ_{!5"ͥQ v3蝩?zv+*U򌇧F<}t_R%/(i+U _B o"k:r܂;}Z]}BZ=M$= ұ<1Q-F8 / ڰA0Աn;jv\/ '8iR)$GV\ɥ`N􏮨~_[P~Azc=1K+> Δ >,0X̼Og?,\&ұy<1BPlVxfyeUϽ)4?ݿCQ]Q v5Fs`vC n;zg! B)>;tGcW"U1R57;}#0z4@!/IM|ZNf9yb AP3_[hpM+j5ThsWw@"mT(e> <8YT>=ds#,un#gVй}!"8H<Ro/Q(N)Tch y glp*`p =)~I ұ<4b3 VðWtϑF (c7sJ毛"ǛΔ Lz#?}~piHclV@X^%(d2y딗W`OCgMԋH`"јڏF#U>ğ AVuded"t_0Pj(l뇋gE"iEͳs A"#ng $*U'b#GDA:"k{SAݰn,OHH/ZX,A 3/"܊` C:m?J9O^V, ŻH{sirh a{O%HG- 5㣩ª  )7 n(zd-N8<X;<hü'ݽxX%N {G,h4 {hyQ)KRS.Z]t,AB.g*bpoa~I ұ\㿟<,X<t, aѽ3 /XN ee~m)t<;3piO5%a7]b7y*㕟ua'̟p3Fl>h\ \JgOg# țDT~Hx.B.Y,ٹ.m1=A 0XussJOF:C pvJr⒒A^#RKRSnȹt,'Br99t ߝdphF:AM|-N4*tjE&i6wY"mƣ)]R(wVlB t,8pK@LIt R$)dE&yx0Rw)2e>l嶥,Xp^E{S.BD:*m&:=+UJ^Jː>4ȫ/%HFE @ 8^{/*XpvID}|9|r9@  + <٠ ᶩm~?G j*9ۯv7x檩i_Z` ` q.ٝn_m0*jui5Ajҫq#x~ IDAT ٲ"s*۹.) -Bs7ko]>/"zPuQ"Mx(wW4t@ah_%__+ p2^ő'o|# Ft =$2y^E5>,:ʹ즬/ЉLp2ұ P㈅߿c檩!`chFj6*r,WH[%z!yS|A@VdKh453]W5@ 8L"۹.mA`S1*j.O2^JAjm& ߛF&QH +B[nw2}' @ 8Zޗz0<6lh**%mV*U"H8 TuƔ_eR#pI ?$”pC*U2NyׅTjU.S}hD ;Y6Yb:Hz4]XU]=pv2!~MjZ Qa xx-*!b#wK[m̮)`_YײұHRJ8Vn5Ѩ 7j /"FG/-Y`~z u_Ec 0 H;*.:ܲ͋;T*:ʄ$f^kamc fGhHN"["Y>N^>p$BR]Rykg0B)7,V*j JE Z@0b# }(ji~+ 2@ EX˃o.MJ@:O'WtR^vP(zj݂k- D>ыLH8 ٜǢ&%C:5ybRt9[PxcqOJ\-rT-rT-2T.-rT&-(4'$D2O"I(~bwa[7GMfB x@n|_@Fw81`A)+&^g? X'R(D=\55lhrH/j5 @&*Ҩ+H&zQŊD/h53GGt83)`-Dw1sj;h~C |YmXH!.IMa?(pR(*jz_An~0 ^V?G?,o5zD"AV )}חcQvE 0V u vZWRZdպ%DLZ} ao}0yφo]Бp" 8UkZJEoEE7P͊xE>͡3WE^~]s\e2P@Qoo IjPrT78Dz5F!yy}XtNi5JGOu-]E6!)_@?|9])fEmPʵɐׇ63(Q\$tE;ۥ C?OPVZ:٨\*({_G>~`ݜ `l`q#lY_Rykgex 5~d}WA:y J2RѠR#^8/ϊ$5"ͥQ HPxnцy[3:pʐJlS`f *Ll5 Get!HH8k)}N8<XE,IMv|7I)R0H} ~ϖ4A"f- J#pPO{Mm^G:1B) v*\jð~2dMe+yZVd?UH>t Jxsi/>npd8Q T*:ʄ6!XWH5ݏ̎0胆~q.9%o} (qxBCmލwC:HP{+hAP*\3h!ӈ^dG0rOs77lo%RK#f Tl>2а!B%tǍ'( +rfe#O,b*t,ew~g? XGC2k  aP}h rN*ў g Lb毛[Sr;H8x:$ilٹ.m" "RKRSs7X)>J?%%C:FePS>}d2^9ұR(0 'J?4ұnUSl8X/h@:A@ +ªV&u.ȐpB2{r٢ @$%ؗzPR# `wlD!qUܚr0/﹎#F=N eWLVt n%*!ͥɻ{Xt,]=^S2oLf'} X5=Zֵg3)DcpOl>h%By2^]oL2HP(ԬR%By(ahφ?G:7b毛,#'\&ұ6R(O,\6f𘞑H \>x-n.ұR(T(w?(.)ұxp#OTBy~w?X,9,X<׈CcTHXPNAR)r2_\>xmIjb1ɔ$©_!`9,x Jb!iii)))?@ rk׮ vWf7oD 111?;m;GGSSf͛WUUe˖>b+.._reBBBfffIII.]zځF^3;T(%E:lDyț&==o--- ]l֭[E"*?oCax^Tš?ҒRh~8ysΉ޽{2e'u^4<<|W4mŧNzB{LT ˆ!h4~$I.Y?N|J6/.. '._X? d2>0a 07o}_z`k) lܸ?^&Zh4-h4L&tt_F͟  k׮ݵkי3grAЩ_Iixqˁnݪ'QRȍh~Zoo簾&eðBb/3wBKKˆ uv^z9T;w> 1$Juҥ!C\ݻ?yHGlhh1c50- - `,rr=yN(E eJJʝ;w CCC?Ç*Ry֭ƞ={ Ezy{575X7BRSS'Nh0v[Ey>H}qq 'l`ٳg*߿_QQrf̘mhhh*/G-Z* ]oiJ` Q%B!#--~HՐ{hhhiir\\ܞ={9~xsssΞ= .źrJuu5 l6y zK.:W^]f\. K>:8׮?tc&:$wT*׵P(͛7jucccPPvt-tX;N:ؑz]ARF uN``/F8}JlllllAxJ=L&;&O>D{Y&edddeeAႂKtaxyl޼Y؉$5Ǐw2="0BO,/'S(/UVv횗]$>xaiM}M 6VkAƠ4I[}lnϳ\.߻wkPhk;mtoQT"}Mdee?~|Ϟ= .;Xo2^ ~HTn B9իm4=dle۶m~s)u._<}BzAF{~Ydn˃ ҙR_~aaa BEۖR@@ Վb)$$D;GAŚ5kNwEE]QH)?sLFFZݼysQQ*"yBC8BٱcG ܅Rn9C: zI |7"fpTT'|)3a#-q.ٝn_RK&GA{뭷w@ԯH$m۶ݻױ!J}ŋ5۟*I&SaرN# fЫuOV!}YETnڴ Pv5bz#kH^c$ $p~̤A 4;s7ko]B&9s?^ь;655^i/ȯ{.HLJJr7|s F1D -JݵIIIqqqqqqEg/GmjjJJJ:{laAچҒ/x2`ՎMY1'! `PA5I'[\s$??ϙ3筷B*6xGX㝕P(Trrrrr讕%oC;wވ 5/]?B w;vU0`?B1f#۶m8p 2Q/4=9?~գ۩J.8p ##CQ4c P(^566d,IϒX(?f5G}j*m u amAPCC>\p 0 >}… RTERkΝ;% uL}8GZbݻwe9Q( /d7o>s '"$5;CX"&yf;A$oIQL#vK:uQ>ydzz:IJJ40ǃ @ p` # L%_9r$A{v8{+W$IRRG}X)][VV&ah4q! )ٽhߙJT_GDua0>>>3gΜ2e RBRK=6=Bppob7HEL,-*`0'NA":G4kŋhW\T`;}t吐);vbJ g|4eφo]b|0B577gggШř*J*R'DbM>ϵ1XJ.-* HV:ub{6y"pQJѣG _SUKKڵk׮]TlZbEVV: ’_u_B.!Wq纴U?0pJ?~jjbIh<Wշo_B3գª]d=QQQ7np4X;R(w}W[C0* }A.4ǟ>}ҥKJrȐ!˗/Auk322]%%\|*6"<(ڸq!JIգQܬݘ6bĈK"e9m\ijr ] =~Coo1cym۶MWYW$zldnBBºual6{^^"J r߻sF9 ?ލoSۋpTSS2HѡcǎH50wz|P@GGD"@͛7OUUի`1~kQpl#T$2q^w#&Xq)s qFF=҄x<~؅NnXZE/Ԗ/_-N IDAT|rPQQѡG zܹsEZѝ?ZXIТ,<;H'M^)ԑ#GDw=pݻwdq,,,0훰YGF.]ZZ j,=~2MִiLMMev M4vTTTRRrvvu6<;󭊊 )cNOR샹Ǖ+( >#j3jdvf͚͛7 Ԉu{ ^p!22W^ )iji|xKKKoUH _tiss3BHȼ[[k\pa޽Θ zZy,t=7F|wP_g!D`hRk5js/qkԤڪlTO-%-f`5b„  xf/'/yb7n܈I`HPvvŋ+iE <"K!LDW\wwt2~US [g:ZMvΓLmUߕBFz}cbÁ7<{͇_cOtǯ[Nkaף!dhh/3KKJ£WK|abbbbb]E\UU% Qs5G5Q/((DijjXȨTIBl6?T^QQPJh;b|6xI^ /$"K:^w֪oyVSRKN<)Tžr%>==(ܒonDYBkjj(j)eGD~;m4=== /WP_s~7oLf<#H_ Mg/yBxxxv7 cK"tGBx#*g^T>d˟c^K*GoeMMMVgJeYm;/\pI&}7lEfx ohh8pa M6hhϝ;6eʔgϞ}/#|6{X:,V;h:=.@ C=z)dwڰi-Y"?(88@CC~DBBBDS; D`(1O!zASBBBNNϷ on 4%IzI& hͥYYY @>>}Z?!sg_.--mjjPWW0@[[K.δٹo)ʐ!Cn06ۤQ+"W@R t}K&cxM;!FY[*++N"S̞ $5ܱXtiݺu[nfQ]鞞‚vvv&H_d|><͛W\)\]MԌk Bݻw?q?ݬU7pjIFRJ(jQ3_\?4SQQQ]C](.)޽{w}ԍ2ፇSff^a߂iii/^>}:^eeeuuWIbv"ؽ{wꘄU^|ڵ8Wa*,,<|뱍P9 ( \ۚ8M3+V<}!7f40Bk׮:,9=*\m4*:bݻwC| Bx"N\CUUQ"WVSS:SNNNNR.phb]? P0Bq|]jbdJJ vppPܢDo.MX{BW֭E> \ (s '#h&];ٳ:;;;cccl:wL$9E@!u9m('\zz:&'+~[^VAMۓ'OR( v o=ܨֱ}=ᶊ*[OS~@vS;L]|922R'ਖzJUi  Oq42/% riKD)kkk,B}O:fx L&Eo}M٭'/ol-Sr_|ٹs\|N#BF8_~Dl|G?Ti0`<|2Y^ҿQRRR444tjnnjBGEEYZZ+mmivk[Y5B:b\~%OOOΟ,NX``O2F9rHPrr2~lϿwP@dee[,#i*x/<4lذ;l"M sPfhvzγKPtG{ uK l,v_u&9l0~#v+[j.'R?{l&u.sgh4{{3fp8l#'_jKu ====V{ uQzciiiooaHr)$šnցHFpueZ|•ccc~*@2EBVG6m0$uڵo߾^/Wڒtj"]]]}~(h<-wy) }^~f _%ءqȑݻwgeeRuNsڃ[ׄ9s̉'VQIJfꛔGKw+=zo&__ׯ_wuu(4KKK_$2)J>} |+*sNvIiPyyyddҥK1 Mp~K :g (Klʕ+DMg+c c^\s~G[v"L"꯿8XQ7%x<͛7kkkc 6-pn^B*L>9ŋB|ucoiiVΔDYjf6lC!Nf畇NU/I8rQa!OP<׉^Ӽ:wL _;Cx<<E-\<&A2qFwZ#ѽ/Km 6nScDUPR|~-_N wtT$)GK.0`\?_|I|577:TVq)!/_~aE^mXG+ן?Q]0gsO?Ϯ.T~ۼ?r·Eڷ#?; 8qbVVInF\jBO>]xK`DmC̻ͣmuuqttLOOխa3%!DWZuIE&{ݦ,xjٞmTE UUUuܹwo*|g...J0A sEqu,0`fWZZ755=|pҤI؅8|vn֛&"Y YYYY[+IZݻw޽Bϟ?綼o޼ )w;IJvlé%A?IJOʦM`56-"8uTlÓİeE;L|ަHmz~{133] k^8{qѣG;uQSS;p@ ^gj~:\/^!N.xy={R]|%9PW|CjZ$@ PoEdee;vLPcJvdiićn߈H" ;=e|3!ʟB:.q1;MQ]Vs" r;ͭu06Sz`ӏFy{{~lHo[</^@ i.ފh[[ۉ'>xeAQo涙)[PXtlé13{(IQPp[Ɔ$&&\Y_Mx˖-kii:F ]DZmaUw+_6UWM ocl]G;k/L:ڸqcbbpܽ왎ԩS;w,B .a7hl jԨQ *j555ֽ{6< ) !aq͞=ÐBsrrP(MMM]<AaqQIFNQ[U#g|멫O!_r75 @Uq,+('VSՀRѣ/tNK&N!fΜ9~\t9RdXx7V%]RRBnշйC΋-64S'dw|~~~>Vvѣݗ5w^^^#G:@<MmZ޽{I$H p+o!=<<\\\~>'Q/Ç C7xcnXA}5 '"0\Z6-: Hj䉋ݰ uu)SſB>zD_D7aXя?.)`SUz𐪪haAEEE4=?/4ÆXiy:^Q`AEFU|~ll**4 }RceX7i$ل!RٻEſgU UF \]<eg`܌ a=zd$E2gLX`ԙ=VMMeĽ{>}Zh/ C OOOGGGPɳY(ϵY;7o)IFGGG[[[ cm''x"kժU:Ç) QΝr֯_?gggx<~C'E{Obbb[^O:v˱?ĥ|ܦA#ׯ߶m8PQQո s'֌m֭F(HdG:wճuX,:.3B}EuYMԮskc sΝ;Wx{{KEEEiii570007nܸq[ۣ7o? -M!ui-hڊpQ !yu@ %M63ZW805v3GTUUu:wy<B1uuu!77Sb uU\\ )Ww^;=|_v̓쳆ϯ!')L(++C/YD>XPrL͍?xan9=o'K ߼| իBީ;T?G,kǎ !ir;jii~gB?L궳q(A N9d O!>G޽XUU@Ka 4@prsdW.^~2k =(IDATY&''!'L]j"wmoo/v~^D/^@?LGjSS/^!p8 9N,$z'E;IdUSsSyyxKeE׵x />-15{ Eϟ: *zܹo+XNol]AnƍF477gee5 Bb+D">~*!Nj+ y!IX(F_U -bGp[/>ʱ ]-[&\7[Z  dE$qA VfK`UH~NNh@$pLӧO20@n:abyZbӧO]\\ B)|rzfT__oaa|AaGS9ֿuVwf~c޻wOEEeرFFF @QvIދ7L5: sMII gwiaB4@eSTTfW+ UH|7cCG[6}Ws !utH:gӬ`Z(^x.ޒ&\Z%Wן$cnX$>>>SNv 0B`Ҩ׃"lJ&&$Ekҽz8ab0ׯg&t tnbQvW0JMMMZ;mW!x Պ<ԈIC)k ~n?fI$(. !D:w׮*h}it HAnllϗ$26ۤ,=-Z$yG ti5  !x\^X`0!XsgDTH7|>lڴI?!8MA#IMMИ4iJ'"W ƠALLLJY9WpjI**K@ȎׯcNJ-HB̞ $5nXn/^N@uqS8|zppF)⊊ ܜ[lsww*$$nƌe(TEKS ƍ'LihǪ[|>Ӟ?oyI1 PٻeE;}w @®\r; ڿjnn믿Ξ=mѣGMLFnK'[@^zĜ={y<ގ; naQb%++ի†l cGO%dE €  ޹/CyyyXDxΜ9}vn^Ksaܹ^ZAޖR99<?ko;rڈEMjp:t 6=_vВi@*!h9pq͜9M~#w ?p=555wwwX(=ccgϞvqx#oL~#zGÏ'EDD$%%#Gaw)TEAe3kKveiX/_xb<WEgeff2 `|/jX-Xݻgٕ۶m.:CD㗎 ?]xs]]]^U\AS["=}ll[| 7HB*DuO[lqu첻J\í',qsFxAvu*{(š7OCpx<~A|6;Ub`LX]v9;;; u?clMMM ''A2@>N0AXRSSo>o>L'OOCzi"WO9:XGy ~\ x<k2nۜ 2zhmm}n72F}MI0 yD$֯_b444\]]^t* ?T7bĈnݔyK.RƖ5M2B￀H~K :g FaJFJ2S܈b/4pظHR7m4a >}ZfX,.K$_oǭ[<<<E$e{YRR"j'&M}ݻwذaE)\.YSS(kׯ_g2fBTk`BBe8_X6֬Yd2B u&)Z`a$ tLinn8P(˗/ⅰL"q- pۧL)@ طo_TTVmСC?ҥEY8oc?t3PQu~" IHS\\\jjj>},z,5itttS"8pUvCU*e9PؼBЍY}zUU벘DSgΜ?m~ *HeeN=Hjd׹c(~ѣGjjjSN?cC:u(x/^hjjׯ}A !$~P@"w=>4qDU**ƹc!*jjj]YYY٧OSDBpq#M붣)S7vS LLLDv*>rrrN<9eʔL[[[y}}}B"Gqy+Vuqvv3gFaTݻ':$ ؜qߴiSZZ=D x˗߿!㈔w%K8pѢEqND\ d"N,9=) 5xW;)q[leffYFX!55Unf.9|*ϨoܸQ__ooo|̥ C8o׳@WKIEEŪU222B666_*++˗/tԉ@ 6L(!TOoe?~iΝ;{yyɦ^uuWeeph'_@RBd2yݺu2 ```^^^ t(;PSL͒,kܹ^?)x ǯXBH p#::kʀPxN5DJ$ 'Lp%6B9zٺܕ+WΞ=[[[ݵkׁJp'Ot++KU(Bq,L Ο?d27]:,р3f:t#DpŠȵW۱ǏRϟ?ԩСC%Rrr MMMM{'޽F_Z7>B!Ry5 ~;:g?\AxΦO<s/ 333ۗgff>=9&n;;;ݻWz̾yҒ"yJBYCs#7Ѧ.^(|RW.%Eϟ?޽ݻwWWW.**ݻ+O>'NyF!'LTTT ӈ g6kWJalڵ(uRyL]]_~f߾}_622ZhQIIɮ]X,@4ybBBbsS6##?^QQѷo_wwwimIIxtå{$<RaQڔi'JceM? [n9;;{zzX/uqx<^T8ivH"ج'|FCp8U gɓ''nnn ,e*DK7Pԛ[$</ /IWP0p o }=\ !$@ !$\ ğxiii8q"##|РA|?;bj^P,X+t rss1b۷oVZuu.]~'LT*uQQQt:}+Wl@%$$R(33C>|XxHKK`|!2'2qƳgOOO]p8C'p3H@R7)VCԴn_cƌ122rqqٶm[llkŦ~Z4OMMf G" hdCCCtz>}֭[gee5ydd)RUUׯuE>}~aefffE~xN E֨Tʕ+>,sr#F8/;^Ac %ϩ*vے1:KV 훓]ZZڥK3fȪnݺh4333777 x*@ srrlmmsrrttt&M}ĉOOO;;"www*P͸ֱ@{Le1Mf'؆Scf:u,P=<oo.97u0ֱ@a:[ی|S8ő+g]:(~D>|Pa7h&p8cp&[X'K\rB$g4z\=態^.hUסUB2,4,y8ySygd׶,E.qY+V@%-s+nh5v]"qP}bL X?ȩ~sYjĥWƽM1e-[pKlE}W.id? .iI⤞ޟMgq+FeXPi%Yi6!k"kܴcXr7V I6?-\ 똔Es9cMk[>.r)hդao{hOM%OДgۡWu]z.ikkkyI|P(B|3HP R(o)7 A  fB?2IENDB`cecilia5-5.4.1/doc-en/source/images/GrapherGenerators.png000066400000000000000000000031161372272363700232560ustar00rootroot00000000000000PNG  IHDR\DsBITOtEXtSoftwaregnome-screenshot>IDATXoLwǟrm+MR WB&j$ h"%Lg(LPBH #s CTԄF3蒁YV(&U"P)\Hm{qp-,K}_]ݻwZ*yixx8c^gcEn?SN>2_Tio9GQoJbb7Txxxddb b qDtRKK`igee-S(A4A;ˋC(>֭]v) DB33M73"b1}r1  d2YOOOЦ(r !!FGDDTVVaصkjkkYL) p8pܹV%H\nooH$ZOQQQeeewޭ)))zp ovSvǢoA~䗟ZMp86M(aeiiiZ$IHRThyyytԔN~ĻC\{7v;ңt l6ÙH$UUU$Ipvv{jZ($͌Lp(R*HJJ3O=;_Sm0LCË.(:r8ØPYPt:'''NgN7Fl6AA%5):<|d>}{w(@Hfgi \2)6mX,^x8OLLU[V\>>>>;;ˤc&2---gΜq\<ի'Araaa6ͻg/5K&ezzz``͛7 l6l6U\Զ62TSS;wT}}}͛_G Ootkjɤp8sss˫ ZӾɘx<ݾ|*}cǎT.䷢k ù9_;~xtxh(aSx⫲oFtbcc_~wSXXX\\7 It:L&cB@ 8sJe  Yg~y(/r%ݺ:_OIDATXOH"Q:dD)AtDC`-Yp; sesBhBD8qA<& 0Z7$ .bX,$IF"圑d+J&\YYC GF#www?>>ZGGG& FGGB|ꊛ ! v;APϯP(jJ@ O z;::^^^%eq|~~tb=e@P8\.Ÿ.U[P(pBA*J& xM$ y^aXK BWhpM 5gƿ\<}JAh4\<<>>6 NNNxjzXA5˲777G2fI x<VF 0p0  ѨVx< ]/BW&͢P(~'T*)lM$###:l6rplll6D"!̗\*X,wrWWJR @0vUBRRee1 C5;} V'(jU'''?V_/x<7wL/kI|(2IENDB`cecilia5-5.4.1/doc-en/source/images/GrapherMouseBindings.png000066400000000000000000000032561372272363700237200ustar00rootroot00000000000000PNG  IHDReAIDATX}HSkǟ w]e jqd-Q3&-) ;(sderfCau ^Pp/i׸.ιl̺k_Rr.IT >R۷ί%E"B!o0$k)VG8~`lX,RӉ P(zΝ; 544p8B1??oZgff^*ɤRi܍vLP)WW(J_]"-tĄ⦦&Vr'Nֺn,b6Fl/ 5 4L4 777zF0+}N2Gl %⮮@PWW'HfVp8 |~iiB122֦T*R|SSS"JJJ8vŋ8-//$hj8?P`Y=,))ijjq::nttBB677yd2!"|}E0 qr4BCEBJMϟ?X,JRRaӧO@ %&SJR;vL$r͛7A۷c,K,(~͐ !#8nhh`ّ`gggFF D"Trr̥Y,JxDJ@ jvj///\.ב#G几i2ym$ Te2Y$xsssq8`06lǮNSo޼xFCfo߾;%~C E^OGGԩSk.]Əlv:bzzzKK˗/ߪC,GSSSK!_HABP3iJA)a{ 'YZ4Ν;ry$2;;;00p墢"< /^kooǶdDI$wޞX|9L'*h4WFhI eAM"B'xlYYgfff:tݻ\P)277W]]moܸ3XUUuׯ#k.ZGjqqQ*޽{71]VV633+>ipɤkZPPGd۶m0 L&q….F؜QD"aaO<HdGn߾}pp0֊bH$2~ZMRWj43gδ ̬O$?|ʕ+(feeiZ.'Qرc-,P,$gF}AvryAAk׶'Op8ٓrdݺu_co>/'''555~~/ o` VI̯/|yK4IENDB`cecilia5-5.4.1/doc-en/source/images/GrapherPopup.png000066400000000000000000000034001372272363700222440ustar00rootroot00000000000000PNG  IHDRsBITOtEXtSoftwaregnome-screenshot>IDAThiTSG{/a)!,ULJY4"E Vxl-"Rڊ"q@놄hFTHpQ,I^^$TI=TO_fwܹ3wA\]]AIYhL0*GsDE\PJGYs ш2G#0 9f;* Qyp\V+hްux~)fv0@}GTDͲ`z-|ۓ54A<ݲjݜ0'"1r[s[@/Kǩ^Q#ُ, Lh,6-S)l?-okeyʪH˞-Gxdp 5 =ӏn=J>T[MhddjERѳ2bV[f:h`5m1v6@"]u\ JrҸa lr{[em<͈ Ws@"ɨpZ+ c2%¼,ē, F'2V ۹^! R-N% ZG یs2:yCh2x)HylnWtvn=HjyU{W iNn?~iK:d|и } ؔIiX2޽T5UA:m-&]5,2ɉvVU9jC5?{T({1ro\k 2ٓO!+#V4ˮR9܇)6 "dC;;%d ~* LwDgݙCw^z"iJF䌴?CRU}vBZm'M܍3NeRRK5CƤ:I^ms"~IJij%E>__:z%-iLr)7RYq#3nuIo@^A].YUxu&9;&!9)>]z dA:~8 sdI1 ur:=Ϫ3i+Z@ g޸ީ"#!!aEPJ0K'g7|Nytg25iڵk5: ^THvp0C\̏ZS$i]{ֺG[BAm5FRف=ki}Ah%~B>yxKK Aј~CGCpKK7}7Ĩ!WċIENDB`cecilia5-5.4.1/doc-en/source/images/Horloge.png000066400000000000000000000145211372272363700212350ustar00rootroot00000000000000PNG  IHDR[iCCPICC ProfilexYgXK.s9HN$g%-Y2HQ P$H "D$@QAT (Hs>߽;l;U5S5]5/‚tDۚ9a_4##-z.sLzW*?3}"@0; a `Ә0˜)6/v0'cok *QE`=DC0 !ޔaM'y^Hy$?:I${{R"‚Hq{MpP% M24\;,hg `vTo-ᾐuX.im=) 0Oz.v}Fӛãl`,Έh;#wK懷_t—blG@4 @` X`p0`N4̋pP%g~~pa٨3<:)I]"<(io]}{-mmJRF飴P(ubAqiJ Ai.ͱ;g1\ F@ ǩkg/pG {ڝ ie<@YM p@*9 ET4@x g`̂` ,D!v$!H ҆ r"VIdA#jH G&!rd ُC"( Ňt?EFF%rQEۨjF\hICǠ3+[^3'abT11.L."ӉbXv$V k%a# :}(vFEMK@eLJBFU@uj='Yqq<\% 7[mx-=>/7{/ߩթm))ԅשRQ;!pPM$<'|'"D]+1xXC|@|E\a1I)M3JG+LG{602N΀DDWLwnn^ފ>>,7CC7HFAFF2c:c%c/Iɔ))iiYّ9, EŔ%%e7+7kk(/6N6]6lFgl؍O@qHppp\Xd$sfs6sNs!$lpUp rrppq_~£sgW{>3__!_ ?~(2! Q4FxA5A_݂+BBB BB8a5a¿DDED|e5}!F;,V.6.W(T!,/Q,1,TH^BKKHKMJkdXd,ddZd> ɺʞݖS g7Oo @V(VW$*+&+*~UTQ4̨l|L[yKEU%\AeQUHSDuRIZ-W:Z]_=Y]}]CE#RY㋦f5DFK@U5ͧ}Y{V_S3+{E^^g}9p[ 4  : &نCF FFEFkWLMtG7j))ٴtL,Ѭǜ`ng^d>o!anf4u!k;;Q\NAf.9 'vNSɻɟ8siߜ3{Я;U6ocTgkpotF&&&'g><zu:zzc&EK^82{opnpn~ Ƿo72}_AC%a73?*,/++ _ÿ|ҏUW?n^c_MfV۶0R8io/[/ߪSI >"z(a P MaB)18n cS(#[0{&e6Qe^`Piڒ^R52òtJ9M*#Չtn6d3164qh`vEVl~AVG.'^gAQW)7w=<ɤP$ R&^i@ `찪]"6آcc7NDVޕ.pLkVZvMXI ~t apI0Qűk$} qڋ___|swZ?-˜T?}9wk6%mlEm_pA#Qnh< jH86c|>Mx@vM]~ {z6{n|H0x'COQOGF3埡L4MfOQO+`|I57 br?ŬO>g|AƧ{=94pC|B"Pih6t9FnQqc ԯ 9}ett'<PLU,ll88cx i|yƂ>U@4RZ\L|SbDJ*NR_l)yOunx~SaSQ-U# ijǹoR댶QgTH/a1I<_(udVk6mvFx!SNNΗ\\\?vO9hv[xAr"#7}}%)ԔOOoDŽ8jqm~QEe݈t98hRX{iJ`:C̅c?g};w$";Edff8iĹu;Gg/Q@P@QnbJ\|k?HQrR6QQWQQǩū-#gookogorv%j&/ڠmTm󝚝'\Dm&j#1{zODY ;>)H3qk R 013IǞggZsɟ=M9U RX,^Kٷ+$ k~u w3vRw\{=pHX3IQ^ .@\[:qܯ׿Si5&~fp1 Ue҃ rh H*rJ@=D}w04},'6sjSU∸#x23$0O &nӊQ12013(lve;psqqpZ 0H(^I_BQ\"%S/;. h\\J]UGܾ^U]gNFR&2S4O8f`adl(}"kӷߟ8*zϕ_kSſ~go@q n-nmmܹ߱_?X_|{S;;;;[p bok%%e~Jk pHYs  IDAT(@ˉ`P,_ ؈EO`ca{N| AQHw,L⟻Svf7VJRL&TzP.tBhxlF,|~:VO|lZ\uB@ J``y؇x۶q nݲ>L&͆Xv=P@)$8$ri`~DzlD 1NE*=k$Y5+%q8O}u;KP5iE"s8I $b83XEqh~w=EQ,bo$p7h4۾_>KH$x5gn㸽^Ao49>>ؘBWWW?:x0hL&qz{{{OOO.kyyy<577#Irb\svzzWH$8Bs:n[.].h}>_8lJEy8&B+cUUggg>o}}vfs$D"t:dnnn =::B0DG$It:EeZ =<<؇ h4*I$iZ,7z(JQAf$h4 5qY:ZYYYZZr8yݾ0LVT*5鱖BȲ+n7͎gz(UUiB hx?.%|gnAh^\\lkkcᲲzga633òTSSX32E);::z{{f2jjjSZLNNq|vv`Zq/SVtzyyP;e2 ![WW'IdB$(`0vVTz^D" b!Il6?==y<T*UP2ULQbyxxtE%uEQ:x)TW#_y.]JIENDB`cecilia5-5.4.1/doc-en/source/images/Icones-SSC.png000066400000000000000000000017371372272363700215110ustar00rootroot00000000000000PNG  IHDR=kKsBITOIDATHWMH*]~uFƤvMhHEY&+lQplD6 (g"APrFŘw!̽]|}3i !n_ğvKmp'v8.v)G ժT*cϿ4Ͳli.L$"d$I\QAjjjEֲX,㇇sssKKKppp011aX~w6屬(JNW.8Ι!Atuuf2(*J z{{ |!4u09jxLd|h4 TWWɭV~(*lZ777Csj4fl6Avvvvww8T*˒ZkcAoD"[UUUz$1 h4 =>>j4T*UZN|Wtw62o8. ^]](ޮ$0p`VL)F.8X-l?ZZZR뽼TT$I655;tpwP(T*U$)N(Ah<xdwK^wȪUa,dg`d!;Cy;!Fh,?^íw6w@\+oJk^N@h}͕_뻶q Iξ{{JO]^*I0\!4=9Q~#W]uoeWj-l0TVrͣ84-{ՔE\x?'򎅍K?%,Hr,44zؗ#=1|Ǘ4i]Æ9<жOwݪ0+ G3'.iN# uWbzم%ý]R s{%1 >TBWv\OCck1H\|\cck;y VYhސ$BSwյKݞp Z#USӁUO^ a9RAQ]'6~NPaSb@VYBvF30 ^1 ,dg`d!;3cdϾnC-}'"ݬ(6<el"yaLb=]¼k " h0dO\:F>{;)ªNbqڻ/mܨ@“3^~>>̏NHٴh]kI""Mm zDOZS0m4Uq,Cn|Y@nuԔeғo굼F1\_$?O,??_fmqPtk@g@32g-(뱘]xIĘڄ[&lQuj C <߷괃N5YK #+EW(>-a;:Ţ1x>&rPt쇛ztCJŋܩtNBHJ}깤w[@,HC {2`2O%ZIy^#2`]lɮ_g( oN\z)?[y}w#:.X!y_=@^z饇4 *dg`d!;# k1HJJz`MVTTwe1IIIUmrW $ZK>\Um+#;dɒ( HDEoV" B;777##k~+.jjFp8#Xe?Ӻ:Ӝ\aXCCCܷ"te=ܓj]88:u` MI11\&? YC-tNTL;k'`nOB.y7|Ѥ 4M7wg̙*(T?z̻D"rGMLLhB f=py"8.rҗkgpW(<+5 -m˕6nW#9BckW7$UR\08Mny/UKjJ_mJݸG 2+=oIu%{c|ˡN k{*l=~]ݗ='Qŕ-/^\4義 g#l^j400*JVrFD^^ưZJ-U*UUUTCIA||GSSSuudzҚ1 IL:d$IP FdZ,^5m[p؞;^ 0%\~#/GGRG|tp \ƦSj':-uWEt:aÆvNGǧY)`@288⒐  ӧr;w?~|Vז⊞qq+E? 6 4Wk+r [ieT>4CMmterPg^~'a@Wn<3Ԓ$fY,Ν;[[[FFFz-'^>EX<<<&|~ZZrww/--mnn}aYYYbr8JɍVǷp:xc,`ÁgwnYa˃Q8bOMrTuJCkH 2xQ1P:Mmi2Aƪn޼ hiizjKK-vorD"vB jxX[1,8zgDjJ>cBn #o|~g@5|3&^Z_"o,#PCs!֍[\%됶 B\+kGho ^IimO8l?G˭VA$*JɝK+zƫ]^^^~~~(p8 DcFCCCw+1x^\d}ḋ~xȑWWW#M__}ۦ[`P(J8srR'wʕ+|>gϞx" ...::F]r\`۶mZ^^^RRVznz$C'A\.-o//ڵȑ#Z)" qDbYZZzڵx K ?HǏ߹cN1WA0L`0,B1qV0 3 1gFc{SN!yfKC6VYlv$I??3gΌ[Z ㏭,Ҡ|v5U(. z`f:;;t:O(_sY)2~X- VXz/BvF30܏qtƆ6ɭXIENDB`cecilia5-5.4.1/doc-en/source/images/Interface-graphique.png000066400000000000000000001542241372272363700235260ustar00rootroot00000000000000PNG  IHDRYX3sBIT|d IDATxyM׹c!;!ȖP5EBeMD %DdoH,)}_`1c̘ǘ1Ý1xL=s}ι,GS\&B!B*_jRu\0L!B!}EXp$bP,ɨ h́ d!B!"TUK*%VP!B!LQb.XUm^ 7$NB!B6 QU@ zױr7ONh*B!BRϤ[I %{V?OޱB0ѝmWCAP!B!X&jYez( / [BBe !Il)>p=j뗱9TmqGI5%/E΂ʟT1Z kğ$pñFst=-ٿ1;qE|r?zlBVsT᧲]wf{ ,JPYj޻B馎 jQl%jamAY^MQjVeOR[h5%"`gIp^qTؓ7_v+D3fi![ɡYdMY|VQ!^rk*iYQݘPfw!!%c8MZ0jkVf_/(R%:6t҆7m$z9QR"S[5+P! ȝ$EYѹŁʞ.GѤT\d@7`sB"*8Pat0a ?9ZWtMq0`[o~)-ZW'7 Bj.qf;HRd86k5)Yi9$Wt3Kj}0|Ng;)[)uƱ-{X{ X 5BXBWIj% B*W.e}dq53rr(&_2ϓ$UBeƮ];ͺ$D TlՄt^<çK)4yHF޸&ڜF/H *2튩- 끻C>~2zLzzVqB˦ŻGRҬ]nRwO"8!biLE s(%JTuv0Sᘗ 8ܸpf[.LnNsh>Kr~t|3!Z;Jz,2*`Z0MZ-jZOB3+Z&}DGZ4< !xQWׇޏ hr1oћŏwC 9D+WN?1tUXxݍ||??{nD9Δ?ÝVq8 XwZGeحlE_TԤP'0FtZ~teEp0AJmw\=RƩc7b^*Äg1ki7NWM;uڄI܉_ sr ·<;j<&{8{T@9%~}Ň`B!>?'/l UPZ--\mPQ1T!+\h;!Fhju%4<8+81FG'hfK9@nO85?]\1dB9^/Sx|n]T| m[دW=D L- WEXc~){w&]l\r'5a*e'|yT4ܚAT#xv$ޤ\fvWE7\g.%m1ECry<ʢs,79uv; Śk8V̈́Mq==ǣuMB! EINd6jAH!J[V-X)V[ 5uQJsnSPEQ,ͱ6+&Gޞ&`rQOSi{j΁a.? ꢦ(Yի'ʿ.z㪨WpޞOܚ>8]WYw$*:4:N =xaO41!v"55)ٕbт͊nxX^FrUcI1_MB1>6%b!(]5m^[3#$TR[Bs괺;4!E–5ԯD=h\y? u!7)(x+GP"NDBӵҟn jf9QTEFΝ RF>UE=whPK7YԻRƧ%GRK|"}eչ;M+sW2 )G¸r2p1g5O{JM}8#8Xv^Pt$}yzr\-Z9>|^+iwm4se=BX S.%ťBF!(Umq-ɺ; ΀X!(5,XSycH[h_6tc{S^pEڭѐESNLQFxojJM((5طU2`B?څGs-9moEҋQ7ټcY[.WƧ >9(O}~Jʩ%L[tx>K\"| !BQ m۶ezcr,FLepuh2x[zufcN}5sv>8Tci}3TɄ끏gvS6_`_c_h ?t7dN"}+["щN{PJ@|X>B!Bp㪪s=@DDDTc$ǯ(Oj+o%ϬZH3h^4MypodD:nşY ¯Da^KNᙚw&˵Ü7wFCqԸv革qa8\{-D-~9I *gЀǨ ׺H~<7joo=̜7Q1p-ٕ +!?~yw|)bٟ>{*#iprGn'k&b?VijspWj_B!wטЈEkhǍsrJzwň6T:~b{oAǚ 7i=]x݋X)jnF rOdC]&Lk8؍P)FْszQ<[QϠX>~[T\5U.82 -Zf՛߆H~|Vø6MπJ6N-G#JHCx UZΎY~Q~~'}or$HsǛ\J_ gܨ}Ic[G6l$R[UX(`zWqeG|[}& >IB!BTw%;wTGf͍Fw)D_'ygl&u%Y%IC;nQ=4(J $-G/8xLe߫EqwʕOр>:Pܩԗc|ԀOM,x9ϻkG)'B9\Vc8r$dנm/kO=W"G- ބ'`Ѥ }w0P:K$A䢗B!RGV$\>3/n{|)WP-i Ei_7ә0r:THФU|gC:;Β$GheyNy*(g״(+޽XzkB#UaUUdj!B XVV+rL?g- :g;ulD`\1k_hkQI`lHVͤFDWi3xoxCGsC@v{%q=*VҒ85y7,sf_$p(D_crn]ud?~.S}Z4r`\j.`zK?MMK*&B!pD{TEd,|l3VyaA>彝n52kZ6]VPUWvLŊny!2͘[i0i5 >ÀMqHwYŭqc3ݩcr{ LȾxկSqh߁^u_'vE܍xY}6?C j㇎?vh}uvV z.z;Jߎxd?=5]S >uƞ#k#*(X^Č0{5P*B! JWܞrʙ \p!woo" LV!G)CX!BQt2d pb H!B!q̥f4((B!(w !B!Epܹ.[6B!B!nQz-8mxB!Bqs>#cbb3!^^^} !B/B!B$T!B!DݻB!Bj,Ӹq,I*B!"WK2J*B!"WrIBB!BJΚ5+7 ĉsB!Bܯr%rw+`rGNB!BrZ\Z ZWUۘKnm.-FB!9HBg͚U"4wc֬Y~/1F%Vq!B!db"O܃*.B! 2;(r%_[ŅB!ă#qsBS@q!B!$IB!B"S(qVjWI!B![ MKJc֭u5~?O͞]*G!B!JBKB;`k@IFJ@~?^䃳b$apsIj֗myv(2B! Cώ[4֝VwGDMh݌D:MrRb0k,FLB!(xE61Q>tCJsy%!OVg+<ʎ` *뮹z'zLC JIf (H#}]d{'?ſ K%'yzoqkٰZjꭗȿ9dT4 ᆕ0SSZ8FEnS!%HJ %yio߾RΛ7(0Ν;᧟;`6[zwܬn"? :Jl85X5+#ˡЖm !J 777ڵkGnݨU!!!lڴ[S ! .))Fv:9z>|8-[0rH|||0a۷P̙èQ4hP*۷%$ĉXh"= _>]v%66EX=Y`ڵ F7UVM_ߥKg.^oD׮]HIJ,SqVY*kٸ$WIAC w` =[кfeo5%{%[.kN\2k9 ֱPLmcΘZmTF啟2hiܽ u\rZ?Yw9ypMlo߾4mFCf۷/-[RМ)wvvNOZbi_&p bSw:ypM%/wOR4}I&v.\ȑ#scv0 L>=2 ,Cޒ%Kؼy3CSNtԉCyf,Y8^OIOM&69|ӧK,aժU4jԈÇӯ_?v_|O~w5jбcG&L@DD]wͼy:ujILLܹstԉxi6lȞ={J7͆NVW9sߢjZ*޽ex*Tॗ^blڴiӦţ>ZuetM^yexeVd]e>Vj<9QqKS۴D4Yr%S?܆{3wh֖ą}[8)9Yo3q%KfԒIoh}{oL i?⫁97??7Ue${}kk0qՌx7ѵˇM]ߋ̚tnceat:w?vY2Tc1ij ]W".-!&XQf T_2[cloNu%>no؁czo-]q??'_?D~~~ۗѣG3{+(|  tzYzUJ<^[.]OK/sΜ9b^c֬YL>>SKhzһ4ՠAؽ{7ǎK_xb ġC'm&*wgg>z0ВҔ|f]"* hޙNvieVO%|%ru}H8u9sL\>{W^/N?Uk"Kr^ `I8oDǍ#e% 轈o_0SXz~z 9͜붧U&^=׮g/$4/cjg?KGX.le?Xѫ1)Ƨ#cg8bȗ+Yv}*,|Ep2+ m_;c8JHN>8d#B 9^nƏɓ,_ꫯfW^ 8~s/3f F+We\|~!'#(d zL8頪8*$$r;oIhZWϩS2|pN8oaرa<{Elk2sӧ쌻;gԩ7.}a~lTT)=;wS]`0שZ*1uT|r;iٳ'۷oW>>>믿fZ~)"##ԩS76fѹsg|I6mBh(ΚFHHh\oUO?G䚅d-yăfSQ{k4BԮ_?)\L7=SaX=W `q$z7 h# %t-QU|aM |U9STqija5W8\;-c;`Jq7K!"цj8=ީxHQAM]tno9Junu'zٌM˕85qF'CCLB +2}! z鯓0t!SZϟOTTϟQF8991m4<<<=z 7Hu粴9sЫWL˄+T'NɚyNp]a[e*Tȑ#yM__ꫯ_bm^ {qssK2[:uI&af̘;3>i[>]rРAjJK`QlqRch4 jGχz32wݛW4`Rٱvl*]*8ZH,4YҪv~flatuu%3m˗/JY=z4_|zj׮˗Sڶm/*ݛ׳n:ziYi֭[w2!DJV#&TN-]Q6mZxz-/^LϞ=rӧOLRR2͛733.]4=߿?|rf'ORFBn^^^$$$pIBBR'^ڵk+W.]pȑ|u7|3};=kBFSL7uj4~\eXcU59hչ&(qEI%_ۯo >:zUn-`"ttNx9icGRa?j7wn;ժUR/|Em9rѣG4iR{m0&MDYj3g$,,0`˜9s&V|L4-!D>2Il&+ Ɋ"%Si1b]MFFر۱cgΜa)[,gٳhYLQyzӧӧȌ? JZBr[nzw萐?^6!!@jԨ>\~X,PUVߕ 6j^֭[s](|\mӆロТE <<<]3f~޻tyi>GuEP''N[jFӭu5&NXung?c;{Uo,KMLv|v Q[1W}6MCЮSLz}چ)%%d;kB`n /;/SrK$Z|+?Nǀ](`Ŧ ۸&e;cPV/weQYL W Dm]u<髁ҩ04Ieo]a3,\pLB|||e_uxoٲ5kEǎׯү_?:vHTTk֬a˖-KB|aR(F$|MN ,ZjCͬ72sLZh4lؐ OA~/jԪU 橕 Rg4iRӨQ<%RL23eʔɲ%8!!-[r_Ѭ^I&T:;;3b֯_XEQHNJʔ`石aV\A`/_ %,fso~䫔(.ZW9T|IJyW[^DzktXxG#K!g1nn.6Xk,*ٗ3GZ;mKgR/ޮ|l }!6cܔ~{l>ͪ (+klxw޽sO%Evu/Œ2eкuk뇳3ovZ.? ubzofB ' Bz|VX,XO`˘1cXhݻwgÆ @ꌬ={d„ 4hЀݻwjW^ 6O? W^%88Ȅ Y&3f`ԩv璝ׯ3h VXA˖-w駟&44\ml͛eEbЦM Vd-^9sK//r|^ h5%/ ֬\>3 8q"f*1FNZJc̥Q>͚5#)πDhժU &00޽{QU͆h`0d7׵kWFիWru673 !Jݎĥ( nz3_on$Eif̘JJJ O7dɒ%&N뺛 |'72jz^}U4hٳ6l)){܉'Xb_}/2;wNOONNƍYuuu%!!*Um6mmV\x1G1Μ9!CЯ_?~Lnr[o:E޽G9}}>vcG8880l7&}<כ9n - -^mI*1F\ZSQ{5kV`*ʤ_~_~)%YQXʳ Z,T5[(cr=*3 c,[8-Zڵkޣv|L2+Wj[ ,`Ν 7ٴiSA^ V\mmJhjԨiYvdcbb 4L.]x|!zhݺ5'հˬ^A_be1GВ╛d4,B!WV&g7 `47&Md_^}UԩCsc /_D~aʔ)Sa۹syo'66OO%PPSoLL _}[-[OOBcovIdz?,zJ bbrt?!DY9Bgs^iBVY(T>ڞ]qF9RxZzj9sL$[BM19|螓zyy}$BdyIB{$B!d~~~8;;*$Tf>$ɃB!DDFFJD*B!H*B!H*B!H*B!ȤOL6 䑿O!BqP!B!E&%J*ŽΝ[̑!B?cƌsB_kܸqz]Zn]q B!Dg*lr@!B<(MBؿ޽{KZ [NΕB!D 6e/_B!ֽLI@KIBB!B=&PW 楀 06Ǩ9ތLg>v 8,*ta1%rDcw@T̢1iIߣLLG[q\o#+wr9%n:OML^rc"B!J+%S-%b<LKZv^uqZ|-H@AOPy|9Y@MũަwiSo:PgzsL?KUlXU0~?;nv-xѠLtOw@ 9S{x#V+OqxkbpEoMĜްf&h5<7] u9l*qh^ى݇O‹%= ȹM[8ol^F18;R*D.$cbGLA*B! $%$D HvM^sʩɴQR͉NӋq21/i -gNZj$`NN$ɜz>8ZMȖDLW/e3^?|WZ1 XqgUK%uӜ6)88[J-*y^B!ߕhxdpGR~ۋMNJe/$ZG2V\ǬŢjpP wtT钙Βtq;&ےobVQ4x9$G#k2q恅K16e[<ܦben6NBt~.hi"*B!O 0((h   ̋ γr}( -Wwb'WtL?trQ.Ԯ ͞[,$D%\eJi8thW zO{CAqBTt@ Նj6bIl$L<|$8m1q.hFQu}J}q;e8~ 5a>L* !BQJ7j0Tcx6OI{Nh9R=mQwaC 0c_XI~D}j~vGqfƙPmDm=;cǴ~cebyUvroo  n՟dx9(XbβcRΦ@d j"Suǒt|̎[q/f7$\E >`ط0-D Ae!uͥ߸O?VZE||<|4iҤw@!Bm۶1{lqsscĈ߿PRz-8mx*UhgM~}qȇq!!!̝;77$/RG>88_>]']>eݨ'x"^x-TمS?ǷI!BLgN`ߵ_% 3f <һ;pl,~>`&T!b݆l kg?| >I}OII|巸z/B!D q=O.$4}.mK%(4TM>T JEȥx܊; CrJ9WEWvh#^^XLBpp& `!,]OK2Y&'E vij֬YaUzȹ*=\.rJf鳏ennnv2AE*9[Bÿ_}ȗDcÁJzƙ{8k .]JvSݻwg޼y !B< Xv-ݻwN:k׮PJK}J&&*֭[X9g~3bpK<o2m>-6w"@Ӥ M4B!'$$TZ/r>^^^(w0;36x%uMn .F!B!r#)IB!B'IB!B"#IB!B"#IB!B"#IB!B"#IB!B"# &kqs%޵ftҢ= @q9rVߌʭM،1\=~Ys vk͏#uG}ȇ.rT;ܳ *cg7^-B!H*-/F̕_W9낱5 ~hQueGX3I.ZzR}K*q8M+`QP!Bq$T0`dO"g},DoSW=m9iK*)gOf !Bq?1Bdr_{^_m/Pk:npHB!P!20 kO3SFJf4^qoi" T!BRJP!2I܆X0L"!$$ e'P:T!B!J!+ĝ3\!w#.'ՈIv{GAT3)2S!Bi 5ZAB!PJ>¼~^{G!s?[/S앹}ه[.ۯ}nB!P!B!EFP!B!EFP!B!EFP!B!EFP!B!E&}vƍgB;BBB;!B! !B!(2 !B!(2 !B!(22 U4" (B!BE2.ӌҺ3ۘ?MG'V%)؍(Sv7O^vhϧs*eUB!BE@UQ IDAT}hS:C ѦUn<:s/&-VYN9961rѩ,iL-+[Kj2RAC1pxG~9qϿ2ʽLt}΢gV~Bws"%8×[#D-!e;؟p3_>>-㹑ڂ ;ɳh uQ6}UqPVZJi`͑D`AeOy&W%re"R̗?WGo:6<<-gz=%Y&Ҝ PirjtO|/+g=/+ _63*py\؆-|zK?p1[s#kQA. $}8l%ăc"Ō˕ķ !8$0 *Bp /O3܆ƞG5@ۯeM\}ug hm ,y;aGypl7[Q9c?'͹o'jCc."""""h(Z[C|vDDDdqfg,hFf=Ʉ k "9Ђ@BLýT}z~IT0C{&+A!!X߽|'TBֿ\݅T#lLvҙhkA=$:1^{4q5)Y<5񋈈P QpӹXسzC8f~3-ҝ퉸IcGk`8tV칇Ͽ̶c^ #frl2 lŵCg׌cZ"s}c[o~\\u_mx?_ٗyql$ybu?߫s8H38o UZ+"""""MKNʿhhX?ᯞ|UH]='rGw6fMEDDDDDNfBEDDDDDgj4PWuHT%H#uMHH 9ek]" TBB *6m)[5 ."""""4L$rr=z`n<& ŋd2-SNeܸq x"""""ML$...#FP^^nZn͸q8s|@EDDDDaLԺ &0k,VZuQFȑ#1 uS"""""R_4*x1f3'l 223f0d\;ԩSk8"""""RB8>k#x< d2 Uظq#C%,,1cw:^RJbb"o&{vaТE :utڵ4u Rkdd2~zNʻヒf;PsҰ¸q z+㣏>cǎo6\wuL4;Sښv 4A' Y)<2llбcGV1˰ah޼96l`=l0bbbHNNfŊޝH&l&((֭[1x`zIXXX,Su\^fwߥ}ҳgOt@DDcƌa̘1'|§~bbb=z4>fFEDDDD&߮ xaȑDFFҪU+FͦMػw/pvkt%e˖-ӬY3޽;DFFMrr2iiil߾ٲe c˖-Z?Rm}mgEM&EEEGIOO'%%B***lh][4//<ӧ;w$99Vڵ+ǏgΜ9,Z贏'""""BNfEٲe N5̞=vd_zZ}С-[$66̼yhݺ5gy&~~SqfΜPB尺'%e9%%%l?a0vXN'ٳOhW@ z{\. _| :t( W^,[իWgϞjhVV1.Zyak|"""""B2t!B坰V2 ;H;v,ӧO?7#F 55r8/99~믿.]?TRP9s&5=EFM!Tj&$$yˣY,#atqҧz2 cٳ)SpsW0|jCn{?|\r}!^p8]hʿh綣[nuڇBԺ_|QFѭ[VNqqq=G{뭷jrJV\U[ `M Bff&:u2K/+{~%W!4))Su= Й$ر>vOXk6IMMvVQ|/(4]x!0WcG4^Cc?4VE? sgB+4=5 IOOjNݩ̖x"""""MAJ݋EDDDDDDjB̑帙Y4 i E:?^t>{`&[Ÿ3m? }0mYDNA qP0.LbE@Z]in RZv(ܒʶpZj=N͗zRZ@a2_x+ WNm\xs"W_ؖ|; n뾏'Mø?Qfƿ|&_}ww,A,|:#`prmSVq𬩹@43cC iLD].{.҃t *y/[;~n^ #/~ gH\*""""Sj\dgYts1/Aԟ%Z^4;hJP~YO^\Rf/ʿ2l)q~5/,Eg#\ˮo|t稤)5T(g蓟1wfܜ>B;}\HPg^ +*%t;,&<;GS~-;J\8AU~iե'.cb5 jq`+%c(Ǿoi""""""Rjd%($ vPn>7||Jb"{&h7{-tPp!>Sѣyg53*""""r̈́6Q>vQCLL #G$22VZ1zh6m޽{9x ]V.-[l!??f͚GILL$22hIKKctƏϖ-[7o[lEDDDDBhvo?+j2(**"%%8wNzz:)))RQQAdddgCݢyyyO>}عs'ɵg׮]?~)))))9f,5 ct:X,l6fϞ}BZ]իݻwr1h ,XСC4hzbٲe^={TFtѢE̛7^iBatL#*..&//e5aGAرc>} 1b̟?EFFƑ}O\\vetڕ~'䖖̙3ή)4jz::MHHN!b 0MoV~PVVv={0e>CL&Çv9sO+fBֽ⋌5nݺ*X>skOo=[oU+WrJC"""""Uj&4)):u-@ Й$ر> :PRSSY|ysd&SNYV(Hcs$]>h֬Y} """"RWDDDDDD|F!TDDDDDD|F!TDDDDDD|F!TDDDDDD|F!TDDDDDD|F!TDDDDDD|R}?` Ӡ$7YQDvZ ɟ}ú<'[6ReqV>:ಈ wXe%RF]wy{odk1B3&0""""" B?Sg^;lKY ͻo2ɩ:9 ϣ4##],_F pU~8\wO@Μn#/ZCDDDDRgϟl _0~<><ܞu &`iw?L,;/:En*Qe9XCDDDD=̄wM{`U?2luCTDЌwط%ZEDDDD4*~Lpdन Q{]Utq5ے%vmSi;?*Sĺc67d?*DZϧymJ,|9|8?ǶǭCDDDD=""""""3 """"""3 """"""3 """"""3 """"""3G{g"r]HLB&"TϘO2 nB?4VCc_4^C֭[*$ypU1zV?0lVx/##.]w%XxmP]ĺi ʵCW9pW.?.&oO`#hk0j9 `xX0 k9qF~ ݁'%^^fd-x.&.;&B+gcrDY<{8ʬZ/c?""""{5ZPWuHT%H("Oч@u\;7fm].L_Վ_t!""""r!4!!䔭uY4P d΃c8a܌Lgdˣ~{<͛}U&xXT**6m)[D4 mCAKdG)M3A5ٖ#vl޼YcG4^Cc?4VE?|)LS6㝺@w`\69[Ċ &3uX.җӁ.zHB3u>x֭[ǧ~JXX٧p"""""BY"Z"5&U.>%ؔ `iA"~:ڶ/c93szq愘wS:t,Ds#7 -:3>|o1q8,*rb))ktv4$niӦUۮ2|VDlбcGc_iҷo_ Fٰa˗/_wAdd$۶mJϞ=իݺuI&y"""""B4kAxLJ*7>n,KNAnqD˸j՘HGCKTK SVXK-=vq,yɩ02yztEL54$.wy3 ҭ[7KϞ=%33zKAA| iiiUyK/|f̘ANNphi>j%$$;wzw"""""B4 8 Go kAL~8ՂtlnQ.)L]lVȚnk&*0Ѫν[[ S@sz/IxHʲp`!&J;nK8gDCU4,&SJd˖-ӬY3޽;DFFMrr2iiil߾ݫmݺ%Kfba۶mz"""""F!g,Ĵ oU1ΣZ @npPТ'&u/xy=At`  }0`\2JƔD9AVZюCT9; \` Naxj?ጏO>ܹy{=.]ҥKi޼9]ve֭SRr!Z{&Wܟ*llc9ّ<ʻ#3>C^]Kk}6(`/+7g*a1^Z:t@˖-l63oDDDпx뭷!޽;6m:njI0 @ **cǞ7#Fn3o<.\x̻--g 7-Ɋ4B fƑ͛帕3ػaÔ)S⋹+>|xiСYYYЪU+.R.f3'}HS*ʕ+Yre6nի $!!zܹsYvmiL III$$$'{6ECt&))hl6̚5YfsE""""" 3AT@Oeԗi jz:BBBBBBBBLBhwԩL:7M[b= iթ+17ǽϏg`ics8.@)hmӗzD(Ԣg,gQ /_LD{rYtWs.9k{T˭ K"W_ؖ||3]>#kfmrߤaxJ 45<ٜV=ZϞbIk(폏bH?{9W=`K9O"K+JʐEjnfcI+{=y7 EU㸱j^YξGn3n-F4A候lD EkVv:vII4BTDDDDDN=Ém@϶cg!83/W2@2,4m&r+1gDY]nrcˣFq)mFɁQByKY0m%xV\:pZvpˡaw` &*C}; ?~?61ޜZe}W81$20g$t* >b=vϤ0q;! @BlwL5=$Nt {,YLK3x#-މ=Tl}{G&DMKx+bL #Ђ~Q)NVH&&3~ŔKc[S~wBVWWωAI>YN_y7{dY`#,_ְb=}e}(/UwDFI}ϪFg&2K.Mdzt$l/+?Ħ +$y&ߏ̓]˒7^M"ۆc+9G_s׷~˪-ɕFY|]ԓѭSuڇќya$_gQ_[ p*S6 5H:&$$eR뻄*EEE:,))KS^Є^xEȰU'x%m 'Mve3]yoBvUGĀ6x ~VԂ E-o<Go{4j^τFeR{G^HܔWs >汷w\{qQ<|Caqy_I\A/m71wum4^SAÌհb.ͥ"F0 A"ϊƲƆd{\,"KKچG6 ]6B;clWa]["""""M3a Y_O+!e{E`ll2 P8ra $8Cm&ٍ-C3MxEyj,ֽ̎r 3`xU 4AJJ 8U-A޶kKDDDDPmVd !("W(C]f}vmOsٝDlX\Y{UF ~vBٰ Ғt 8tm4z0QPU5zw7Rs>{nᩧ=7BY P >),h$cb_*ɻʩ>iJ4TZ%ia 0|:yI64Om9.o~*Fvzf3c:UMJJ"!!k:^T8JzG4v^τc_%TIV1WO]xp`Z hʿhG|:*"߀s/熫Re233ԩS}!^xXؓPmҸaӹDž<4̂ |[ThPcw<ʏhh&"--K] """""M C!TDDDDDD|F!TDDDDDD|F!TDDDDDD|F!TDDDDDD|F!TDDDDDD|F!TDDDDDD|F!TDDDDDD|Rfº fMvaxl;X-/^aف6\qhst|׊;o; 3i.ӎM_GmdYz 5HRPK?pW}|Azo14 fδ9Tm44Ӭ_}Σ"Š p.cJZ,CziG~.͍ԃ̄Zi3$Xzr\@jphGFsVg*Z,u'K <1Xw=s;vKKx7@Ӄ,娆f G'@yb}HGJyeo0 g<Ό %xN$ҰX7_<0c5]x6UP6ٛ*""""M[ fB "ZQ/"X_#kCZfN;ΎNbH\9?Dn,kyL]Wq[Zl*psFi)= ˸_[M]6"{<%Y&9 O;? Xˬ~{wxYW?""""[5.J4oE{㭏@X0a.f=O97$0 >.awQn #𸞍fwic Tp=Uテ#m<8L!ф"cbƭ]\e!!l+Oܑg$Nz͛7eR4VE?4VCc_4^R!N~Ë?9nN9m&OߵЗ'7oy e>sZars_Π]XaƑ{G\yKy^?6|$Y@vq$[Pɜ7go0 Wq.yl^ud }n|~Rs7oXXx_cAYNgitSbvo"^ qP0.LbE@Z]iΌpy\Zy' """""uPbKf}ŶɊ0kQZo+MK~K/d3y\d"]~^0?GtHCKR4)B@{>C<~{gY>`5ߔ'""""RPi뻄JKKc,XK*(h< 0̇Ϳ-q]ǩ\}EDDDDX^ЄC@zzz}P(N:UMHH߯Z6"ۚ1;sW0}|vT?5f.m'ISKK7ְ}1\=ZATDDDD5gBCC#iT bׇUvx ¹2k?;W\.(i_fhvg^kTDDDD:|Z zŐN' :'*.h۳RPqoA»]x9)QDؙX6q{٫Q0/^2)=VӅdt@7nAx(sq1YFEDDDD>̈́J x9'qRZnlⴕi CK 8U-AV\5z QO!Tjࠗlӹdi ]Xvs`GmGe?۔sEV*m6 ^kTDDDDӃ N0 Fz)U?5] i70~eYx[1E{w= #QP]Ƭ'0 ÌQ+لa[0o`d{aQe{>6kU)BhRR u]4p]AΎwHcL~9.JFEDDDD>s} """"""MG^(MZZZ70~:p9]'ߝY`oQIKK~Hç""""""3 """"""3 """"""3 """"""3 """"""3 """"""3^wީ:D:U*nI9]im= vEn3SulEnBDDDDDFD b_E~⽇/J9ohggᆪ6wC(WHD@P'6PU@ M){fP " "M5Nם$lIy>xݙsn3֓~qp?KNFL(mgDlr.f~q\jw'. fڴ kft,DDDDD%q7C|,1OԉĹxj6^)xe=t# QĥXx/nQOsts .cx^k,eK8k͊C7Fٌc Q[;e9#MQ㔮-ٻ>apDc!"""""$NojС-XfExpov> N93Kxl3F bq ); vw\KlGiPEy^%!>]Lٗ2axz>ue2LM lnbW)W2z[֥<޴u FEeӧaևf\n4y\\oZcrxn[O^f}2"WutBBB6m]wAjժe}ɝѱ_tC*?lyGs6*""""""r'BEDDDDDnBEDDDDDnrU=$mZc[EnfAEly|EDDDDlMU@~MF+u|[_Dׄ((7aR/<-싲Xvn5\PDv/Y'kmzC_$ɺ+tGustDDDDDDl&q-DdCH4I) DĤ`X^%t xY}[W"b?lHҢTʊHavׄ<+O;Dh4GuS;g&CWe#N֨kY "BC+>O$w.K#ՋP=љ\h7SDDDD$7&\KrϥVAT[& Ò , N17k {°FЃڬb]:VWcX/:^*!L&Ot7xugY8'+ZQD%)VѓH,nEǧK55鷤)3jղy?}`=ѢS/4ҵ}&J4ȠynEڌB 7 9]kVzEH"dH -iX>ʋG""""RP;\zRZIyu.]94*""""""vHGۺ.rN``lu|EDDDDlO זu۰e@u<P) rtM:ؚ QQQQQQQqN}zH!P#>>>d@3DDDDH؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍B؍sꋐGCD2㣶)""""FBE0P)h8"P)h;v8"oooGWADDDDĪ4WDDDDDDF!TDDDDDDF!TDDDDDDF!T Dߜ<Ӹ<ڞ1ϐ|'뉈؈BHg&CWe#N֨ܯ'""""b+ "^G~݄˳hT™ȓ^DDDDDl=pۤ>֒AlGt1xl.d _w&O|בQ 9~> &HێXLyXf5an\lzeG[rHHZڠP=)\ODDDDvB%6õlu>ܜWHlL܋?OB{xi|{{qyg0-5ΒZ^ǂGABjE*դJr iCbFF_qiU9ɦA\Isګ/;L&%2&2琝<]X/:^UcxI*P.lg3ɨ*qo6][5eÕ3艻]ƙ-?%0R∌ǂ*Rd\Ocnڥ?~& =BUaZǓ̔_LgU$%([ra)jժedNׄJMzߣ<Ӥ,pœ%)wOCW ,g8tcy9i&MOQ8n8Ss7xq=߬7[sO!GvyT{v"-ćnev~olK>\);,v2/+7ψkZYJ1?N%ve#]>Αit0LYUS˵[,^fu"AK$A2߹A+P[,zԄUikzc7x_m,jWyPˌV^c.~X7-?o+-J®cρMbN/|>K=;mjQ$[_͘"eZ*Zіe[%-4~ƏY;p[K8 ZxPm,/X]68S;ѤzF0eX=hI#+Ƿl6[ uA.pʪlK<8vsF:|؟ăxWNN%?Lv$6G7k {°n@U^vRʳ\ߩ63&nbڶB%_2Qm9Zn\>ۯuk;]Wg6.9eZUZ}V~ڻxve/p\;9ڗÖ*Yݺup1ɂXƵpuז__ׂa2n,m7RATԗ)x7RgTT 08}4˗?]:~֢/q lՙiɋmґ|n4k׶D T_`N6!ɛKD u]$$,&5<T7mǎ \Ƴ[n?3g]v*UÇÃiӦq!<==0a̚5]vYq 䝎?vUOʊ&\Vz%~r<֢6",I$+݁RJ]m7{X,9\ӡCƎK֭`݅"m-#rxE,2r/fƳ]2Ox @q:w^t_ݱc5r}WnF>1L\0LIHAL3Ր|!ԙmKά5^Oѿy2gJRq{2{9S\:-7#e˖q_5Æ ~\W2?3S™"q>oG'q;qzz6_^4Vp`>ߝ˭hŬyγfb7gȦ2EuL:t2@&.ãR5sMf}YE'jK|=#i-B|\0_NAp%Bwh[%րNè5X4#rڃ_ЅQr`/\_m2]K{g\8Sᙬg,+sL k/~Msm]nLdr\l&}<ž 9YÇSrHLL7 eGA׮]k:fJ=_G %3+5l1 wj$]p9ًVCAùlQݩq-3cL[ôj\S^>+#a1^_5@U(@UMDƺj^؍L-J CC2{灏f*T<{K&a6P+hoxRȴC)U]pF2|*-9.Cxxxp!j֬I9tpcQ*Ox[_j3Pd݊M=70}$B$0)92-ePo5][BR|2e=1W8e[qBwt$gSH'߅}ȠwS8s.Sru j"yGaFvh"Mc31'oO'{Q/.Gߘe$R!>h) W !0S$>^}&F}B.aw x,=*v*n*ɔ鲬4h4Yfѽ{wj֬I֭YdU꛷)lDqwc`^xK=zW)Qnk1/N'Y:;/gӠ*ēN°gﷷhp?=p~W?^2Nqlۃ?nG{kHO8E|<ʕw]ɱP?,d*n`` FΣۘA]pqEo6yDKLqkD2qqipr_$եȿxXB5!v/tΔ^C:s+p~{r2*|;lxmBh%~|ѵ72ȵIaf?̴Ϳ0bMj#y=GESɒVkFӓWoN_iѢ7o&44뛗EmiGړ6y:91ffZn{ےWy&gȌX͘ui%+^sέw+e۰ysR_3_c֒pcζ(FPG~7{|ƾj8EׄdK^f ٿSNLX_/$C=ckftq>KA?|{.T]SOT:>I\7G26v/#R(QkTÇL20{l b3ÇQmݺ5JСCׯ gڵnݚN:ey'^ɏT_m6@zFhh5z ;ldG@ g*1to^O1zOQj:噊w۔|ӽ=J375>P)pu%K2rrJV\iI|;]j gxַݵ*k6xfSDTݺu?I"尥F尥ꤊQPP]^~>h7 G0S_`NfF>>>ޑ/?}+FGCFHH@1ɜ8GD@EG}%QHH^t@P>;H!QP_5#""""""kBEDDDDDnBEDDDDDn|Nc;.E |P_`NH!T$e'o;ژsDEDnݺ|} &Xd\}m2LޛLW׹: L7,w|hLBH>/a'o 3"|R0v?߭Ng?vڶQ!L)MR#\):]:hh@H(^}a$Eqݞ]o2l@jxzb}Ȼ2"\o%Ί㗭uwAF\'$ k앛AaҭD0S]aaL9Ր|B!TD wlj"Ѝ_?qr5ljk9 I>0ˠut)<BfL>*<#/mZSNeii 5c iPiL n) \ݗKwaϟ#q&K +UvBE$C^}K!1y[iڽ?]p2dTM2QlFÇ'pԟ; 囶Ħ{1Y8SJQ\ct5RH;bٔ7O1~ghg=kS IDATկ$RqPX O:Sl,kgeeOoҕi^; 3೟9t{~k>&h\B; ֠Q)Lw@ " MN'g1jA*vaÑbyX&'3&D?b,ZbE 7%+Ԁ=!sOb7bOYfSzKCSx-f]{mW4ͨhFd4T2)L0j"rec[+R4W*Xp"v4FY\Ve.OPXFAth;(HHfC[>K^v*+S>  Dr OpX<,oe3.f$ RKYoJc ¶Ֆ:&(%W2)Lo=&wq" p-t.ᆉ,NqbAXD;UH6sp3yO'*8 &g9gJSKƔ SXFccFX4mHb˴Y~28DՁ.|7"ݗ3ƌWkaJȪ,koLeFWJɿ^,{}u&S!"2b`޲1l8o圥_ܜL d #ɏ4j=}nG(|Tvp:woO_hv+K)s8+Rb"!Ⲝe.=cnw{&.?U "BN۞.3KV<_ߴ:77y-!-eyˋkR_d'n[KEBc-D9k'L9@z-θ~3㮎)Zؾz9L^ѥ LZ*D XcF,ʄ9-圜9AGxށSE=tb͘>m:+| "9Qq>z!iZ{nxNhYl[t(DDn'88خwuX{P~ou #ON9ͪqO t[ 9P=xmKM![Ҍm̈-TNݺu?}׉؟G.O2[)3\u`DD(((|? G2LFBE1}y;5_CO}';}#P|N!HDD "C}GqEDDDDDnBEDDDDDnBEDDDDDnr|Mm/vμcZ[u,"b/~tGQ_`|}}y\WH)]rqs^d߻3DE֭˷Kb6W4ts[>A7Q;e Lmf+ 2yGN[ޑ aԗvQPL |hY&az!d6x^wj׮YT_ʬoBYj6nk""moKn6e*= "wi1O92-xq}`ND]NϿlJvLoΜ"Mv_ݱc5r}WnFFMBJGWAQF2jN`H!"y[޼4HB1^/eP`ײ~o~;gٜJns"'A43igrF}}};m8N.wTǜ\B%wN~c2xN0Ʃ.o6/c) w"~&;9r50i'jb`BFS>G s ѓ{Ah<1gkKp9c7jkuTg-ޝJ=0`?%2d0ՙ.;@!4Kͥx&X _&պCf#yNDWz6"xptI^2wBLoOD^})qwkp=(`FFٟrv3'!¨KiZjAԇ 8@fLӆ]9fs*ٝPgwy}ÐV3f Sj;ͨhFd41[D }H^S¨UHqxr868#g".Αy2&8";S \l+Q % nŵVԌro[Hq[I{6̾iV,/ eӑM*]XS~RFu᥊bCz\VT͑y΄1,9faG3e{GO-şC@??0fy3Xm7oIOc5վҼ¨G)B21x+WsѻJ.$Z(sagݑ뗡$m8^D8b<cld`qB#PK8>'sZQ*q9'ۻ"29D'U~˞gQhSβ~n~̺]`H=N%YL|)q x~kr@}2O AwǕz6yDK WӶcv/hω$·Dqw{>Jz% r$SQ*׬ABHP3C)դ)֞k=Zގ_۟)fArLWyȻ+ |<`X,q 7~f;o^~NѠ4O솳%.p^ lldq#9N7S'i\;V fDqw0yɗ?=&4bv޴piPL_X[N>y_'Lv^lѣtf}t\ɍ^fa Wq3i]2WYF,GubSGLg!Gԏ }3[JҎ l 'KG9Ř?OQ͒O{ưql~si~1\Dz%H;va##nZ-G3}_ľ\,1ɗa,+WIH#g褵oM1m0VyUV*/;}#P+K -*"ě|"LBH>$"R8($JALc*"""""""֑)]lYɆSl[ސ&""""RPh:&9U9&TIǭut 5-֏?u"Lp(ԺX *Oխ[ c2,`2k`‚_]H%vF,N{ݮS{nq.Sqՙqmqsސ׾<(V,0" 7Sϗԩ+Awy-;SR0wf yF@H~>~Zwǎ4j(uB^5xNZDs2]5x&Nrt5$Q% MG/ǁFrs||R dVcXc'ap58=>7q*e-9 ɠ~0˸4MFʜ-I͍؏aMJtS *LyYK|>3#Q7Nͻ?X#^dq`~L@7OTC^g{r iy u೘P>:iGCZ敝q2XAf==DE\!ϏIM"90kwew_ePb4G\c|^L[Y(G]ΐ.r*4*_buϮ{4eˆ+gw. j;0j Ά:"֦v%sPřR/Nɳ}{w[ϫOg܈"׿~fNvqd8=շ&8Ϭɇ&ZށfӨ2g/.>g2"3?cVewYM.km qQuk>Z~K I"|Hf܇S7#q _wܽ!:cϦSZ=]KuҖ]oWGͨhFd4TEOJ$F=xB&C0$\_DŽWiYDqȅ,}˽ow^ɛuO "b#10HNH )!,1Dś)E^=K㌳> >[j%5m82e}˸gɜ-@b.J6.r:tspk${>J{5"mXczЈ]ܹB;E䂻g qW=-I8qǔf#1#:/Ӿ*>is?dmoOZ _Pq8.!|)f'L o:rf VK K'?В}6||03^ 9raZ[PN# HeO&L1Ҕ#d)vMԮD\ F"qq.xqL+BrDiD@AЦ;&bqzv:586p(q>{a7l8skг@6\ΐ/ߏ bp/8~;ոܽ?s)ZŽ?}sQ6EmG.\%Kо}{GW#'dv_I$&௡]ĭ ?'PkPvi )R,Ǣĸ}Jw "S?GNM5ѵ)0,Y*_!~lčkz[2LJF^@@HR/z 9t8ɿZ3/Cgi3a4z5J9;Pe1TR| .G/N)\:ĕV(]=)[TVz2k?6Qqqj229QL<0ݸ +J"n,6'ǫӖ=# 2i$a)(_6_.ƣGt׆Sw#37EwGi^V#X(Ni+k!1._f9*1: vc˕I9Ofy$X +EF2d7fNk<'4,-:"yEpp]$G@@ofeaW3ko&2l.{!i>ϚU͖BMaYI;SI "%v0zǵRγe}<W&\@ ,'q.lJ11eQJO&Xֲ'1c C)v<=4d}<}#Mu-cxzy5/EvL0p5\NPq!jEFYD)KZ0Z RG(,X@)2L~(@tsMosv멐n;u ׈~IѠ*776N!:pbG BܣݻK@(Ţ˱>IDATK/SX7*qo1pfjb#8!Md h1"CFDOj<;~Cw;º m,4CTGENxhErȱnn׽~dž}'26%}~[[FQ kMZ]יaTBB; B8ez`9_N>Th,u3ՐJÜP5reb>;saΩ-so/K:,P96Ynf8P5`Mc0c,ENf:%aTT!BҠ 2柠|X( $nG߭nGQFEB!(JY5;F0S B!B;*(, WwEXtqyk}m:}x@<:6OǕ/V$MoZ<݃:Ѱ]V ߙŖ;jG:-`.߲;Ht!ڵkwebz𿏃OFA !Dy1}!L5*(\ucFl5I'U5B8ΜWQxl E0>̖0'#gHB~3b]?^LgWЪAM1_JȀoIS~\a !}IJ7scWLkIBq4 od6*`vּ ˘8w|Dž0|D37W'ߛ͖ I~è=haI!MoFR ^bi8 1k1[?qǯ 綛ݣ"Gf2HBqZ*6$uC"9nLY:[:̍>ևh玷k>OٌRq 齌Z--m B8KG¨; :U3d9OWG6U[xҨFz35nJ4+9h*֤5lrD ݜKu&x7 ȴMXC]NޠmUJ=z)ˈ+Y$U{LϽr^!KNUxzʝ^kVСu <7.0YU sW ^+Sc nRޫN鵍M!Tn|r^o"p[ޛg7aܪx-&|^ˆT/!gO-1 !ɾePddSA(Uu{?~7wkE򊱜NWqmX 7l$^_C\m}HVP 2%CDDMF[`vk& b1dcξX=b4/^'%ـw*h{ AAwkTH&bڀ/lnBt KF:Sn-.ʠ u&+r62;%ߖ_/r} :OW\=t(M(1P꿄ߑ\QpΘ޺Jm& !>5k1dm ]aSr7K=vg~6aHf!DUަ_>ȥWw(6qdSv# $H[x艴zZ 9KBX-Z\UעS ͈6' 8O$!T!z{v>yGѡBg)>#@ 2'!k?~'7XxH*G05zءV*Nd{ q!DYҢEbOBnMr϶Xf :uOt-b7׬Pwtjl:jt,Zy;SCk:=ZfMmA[Y|bnVr{7ģ ~JG5eݺuROdd$cW+DƮ& !( u DGG;@2e5}#'I谓x?3ςgQ$;W{G==n`%4MP C=e9ue޴c.1-Ͱvͮ}y"桗M>󓝷IB#::w9a=˔=<DhR_IepoZO.+N-}|n$WhN0.`2Yep<9`[-*Oc1f߰yljĺRck{gf"5kxa\nLϢE޴ߦ}$c/дĹů+4:]SY'n*VvGBBa= %eLǽE^G|]ߣ|Va$vzW3W3qxWŐy'#ꚽhEfa&UNO"m Y/oʏC ؗ7^hI`ݧc>n5h6;CS7TM]nSB!BF0gL5>N?q%.6&n\G3E$O`hcy=s_%ͧ6]T 陘0v-mSxUqL#`"3)O-EArzpuָN_\1@ Jp j,\αG0}[4q1Qn:EQ1w)GW'aֱngjRO!B!lsB2:(΄_}_5n٪GnPooNO? 5\LPa*&ذP]zřx[y:n3paaTh,u3ՐJÜP5reb>prq2cn>L9w{=v)PG!jg2jKVyx}nsVM>ª eC*ihF/l9*{L%,j. fw/fUL]7%kC%b5o0` Y$pivQр AN&۠lkѢ+ȝѡ||^,u9Wع#v.J ʁ7\aq*3pc6X̐g(QQi Vm 9[ ?еkW6m؊O"}ɚQ) fw1vw2cvyaT>'rrKzJ'!Di6܁ $ !J} #Kw\ňmQj 1߯S(TzbC|!dS)YϽ ?+\dw+N#B,8d.fRB~Tex~]󏦳~JN; C@@3C~|1gcG}hphVU.T^Gg2rer|ΏgnaEOf dlVzH>J炨eZuG?1k1M7B74⍕]STjT¥{"TtM(X6/.LD,[ÞZ|ŰTuesj~w{SIz>KɛU3ض`){RD\[Z>ޟ/Ę@nձdtKXK+BQPxx8QLd"|21|ҫ<}{olEO{'[MxyF8ϽnW>hXsEz>ֻ-h/LAOxG{eLZUov:oHT~tfl20wA~-.bHUcB |}jc[ެ['j]\dtKXG+BaQKT2"T3 i&7ZV! dz.$Q/.{R0z|QK|MN1W1k_dӠ͇]Sg,&+;pp@^FޢS1sPS*h5p똝+Uki? 3iPUFw3>U I;.~TBWww\5 FTLdzRNE.vLsLdsjjj6D4ێ%薰|WdM_9Q3jif&ː$FZw.nN>GG` !tkh3 T3*n(hpm=/Y͙*C>ECQ4߃x%zwhv*yU:~4*oaUȒW1dx\=0g1@±X:=ȶ);I)ޖKB8)q+G'(. .Y`[l!Lx\Ek8m oAE~^?ݐ/gHdҍx5=r*$sMt=?wz:@###߷+ZmûDy%#B.^}'2Ž݄?WN\YnU4x؛KZ;x59{ҕ H' z<):MGgVM =SnB6Be*){CRx Օ|~Zoן'q2{NhBJ-Zk}BQ^h!Ľ$ ($ IDATxytS6Weɒ-[ K((mȞM2L—&m9闞,=9L&Mڐg$4 [&0,Yb }Z2ƶ^ے-/+=|BPBȆ z0lڴI>ۡ8OBd? ƅSN~v0>! `@4!j)Vy͕Zrv8Y8rq 0vҦm kjhhK'B(XfS'O3k#}K^ m`psA4yT_Ϛ!|B{o)L/}o_<4G!1M0Fd3]=𧿟B~3yp*9QO{ɮ}+,})޽I!ԋ{vwCX'dNFN={}b߳o<}.EvҠ ˎkI1b)!Nɏulݡ76ǡމ c0F[u3yOBݵ7ջJ}5م z/}:íG}7=ӚJv $J3 z K˻[~'wnc-?6ۭ|j۹~BK[&O?y*pX.^_;÷GO>7}{Lni9n( h@S^JS'~(˓p#~ |N:Ooڴ { hD0  `@4ÍoCvInYϟ7o鎆 `q@QTT4〛UD5GXv0n2NTSjGkkkϟ?p8|>߈#&&&`P(^sD7ac ) 9 ~M8Q.Ԕ?裟| q[\os8RA!"!ϫ MldIj8|DZf;{7NC.^Ԁ6oޜ ߗ/__u]NzWXO9s߶m[~~~E)ؿݮT*\.ɓ'׭[GoqjժkRIJ|HE(I$(~Q4w|gX2xna5$''[nOlذW_s͚5{JRRxqAXO~Gy䷿#G|>Zv\ڵk[lYf/˔͛7Gj(3gTVV_p;w.77'ꫯ\.p,,]Zmۊ$QQ _EQt(ڴ*- o_zÇ#,aH撃Mefc.9+V [ {T!1cW^m2'7)))ٲeKnvgy_,((\tNQAm /C^Vϑ$*IQlK0_H.j[mʘ~_VDD'͓$ի&bV$8+;t` O;@K J U{ /"!dg>"oQB!o>?O-Kvv+RRRv1j{_tRgii^x d2Y{{ZcS(j7(++xsn߾G]fHV(ʸ8$qGr_$JyBz:nq^(JGDQVQaIKdeEJ)iɓ'[.\HQ$B$:&CZN9C^"*֕8NtTWwtZ$xٳW\n]$M;g-,ϕWNROCE-~撹o{4̒V+!ISZt|QyʜǎXt)!$"W͛WPPVml?oܸqʕ¹s^?%TTz}z%=rF&|駟~Zd%111 BI`bTEN篭^D(ΘeC(%0F-Ba1Kt%S'gpiLwYERAO:=#(S\? ?.~bflqZI3AbbbBs޼y>˗ϟJJJ邂>zL&я~son?111˖-/nsɴuO?T{6}L111DFۥQ'yJGG$RJqyŕפdI;Y(4c4ƄK?piҮ)HB%|>֭[xz  /66Ϝ֭X,_{{/:BvSOٳo lWl޷oG}TPP8;z0رC.5Bͦ<`ZyecFQNδ"B9At,y%(u:ii∏ VtD(='z<='Z,{l~V07p8~7l?Oz>!!aŊ۷o߼yng~\1 vVB?gPVO A+ۨH+嵖Nc3#C߷Mt5\,hr4CľKY`oIft`g.BtJSOUqiYiZY?OAr7W\h Z-w8\rɜoW:G !rrADMM4b;h=C$bt PޟA4V\'\2GV|A?;՜GzS+V%c+!0ʰ+)!fxC8S4Tg.B*Wȼ6DQ>GٲgF^ltJ鶫!=]rYˆJ4-6mZnRFӴG-v1U{Okez+HN  F&%xB -?'&j˚=!t!СÛ bӦ^-0Tdi&zE.NVgxKu=.^K !yܝ"!H(Tl {Tc3;k+>QԔ^iaMU m)氩R%c=g4V9]!mU v)6qBfVV -\NV7cKmnIqI'vtHFL5ezzLcsSWu |v;;p!]a!Q2%;v|a_MMQRB>9\8~dϝ[\rVRŧ'8_z, i3eEK>}>FYS,%Gq8R=\ [O &Iұ5 C} 6n%ݔ䨺Д0cJ\}x衇9z0Hl.naĤ|} \݇cZ*J>`\% B!`g2*y+y1<sg *тRsg`=j5;ǀS$A$q) FVCCà  hD0  ``XPluPl#vQv0PluPlvQjx ̎=g8s+(+B4Re=ʫumn+ ɓ23#l)-ұ2޼PFbx^7;}"9pIBW.ug.̋UpBњ8;R":7xdknfT M îb;jKܴS>s]r}ԁ޶EuڔmIQstWڅX{\vzyNyT2=oQE !K:_P2_়1-ӡuD(*Sp_v$6lkaP zTI@kZvr-)KGdonq&m>FyM7^.9aN$79_[cee( ۉ=ꧮ_;N6Dnhq!Q}ڤ^MyTJ}ROᕚ).ܼcGq!d#SlGcR&:*_mqxI;r`kbw=[T`nGsh Jnn҂u 3s&;Zq<*'NK]Z|mBJCb8f۝DrPc2'+)!7(vܐ -bdF 5!1-ӐCgG$xڛ tAPl&~ya,^GT.*.4T趱DPb;m(R(b(b;Y(:(рbb;%(Y hD0  `^X(%U/b;[EX(U ^pF(T,½c(vh4#/d.6ܘxſ-Iw=u3G7gdPBqJlv1PpFBaT]_-QOG^=_fj:]WW#K%m5M|s@4s;I&;G~|_YMh7r)aT]_Ot$^ QIDAT~#/xqFb7豇c#]0>+_IxI֡ RoЫ;RA5<[qbQwW$J͐FCX= VDSf̣ ɫ2?J:0κV_֪:DžS{7Jot/Spsx>~ʽy\_ϚX?S̽qZRѡb/Qh 3' !aTлčUOG^L=Vܠ^8 Ꭵ:DTm?OK. Ldtۍxw#@S/d> X. {]DpE  = p]^0J7zAªD"zABF^hD0  `K* U*`ګ ^5 |/mFEXW(o ,欿xRץHBWT*Camu 3$sEy.m9+IIEfE!gzrmjRM3(s b&Kj%,KKrfP"K/ZU|Ֆ-]*Qj{,%o#^E< MTUɝhF<0W2KZyƞ85*!A.5Ke/ؒt5&!AX!+yB"/^+mL5I-ahz}zn^/ aȷWIv]:iJ_5i(<. ›:%GXUO ILԖ5{$B$ns 3&9qgϙ ,CͅU003uB$kUyzsJ&4=ʘ Lvk bM=~tDx̫BT:',=Bƪ 2XG\\$۫VX~DQ$ZUsc*A7-ME 5tJʼnƗoʔLKɹ8]jg p3cKJwg'}5ފ ]rg%*93Ŋ9=9aJBq.&ziHFJkϟm7:lyBЧ*D㣽*pPjCƌrԨDg4Xv -.U*`@{0 ^ `@4 QUګU0۫P0FFHqc{y70r7)F4':j]cHr59ӍQ-TΖmuR9U\uo墊eS¥_MΝfR!`%a,  465=T]d]V>?T2X=\6%y+.f.ؠZ 0kP'U7{ u )DkHP)ŦM=qh-X0_yMu͝mxH!CV@ ~.^شtE:yZ\6E8{꺮CkQ D2ꡂ˦2%;v|a_MMQRBZp`b{jUaA{U*`@{ F  hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 hD0  `@4 #\re㋌9hD0  `@4 *f=ޟby~^W՗_8?`4OjW u2&$pivѯ2ːٮ c>j!ΊNBq(A5O3ق<O\0x=߲/%Irϊi2ׅvWvJT$V@*Ow48sL]nu|TR.iݖv{ZM &D=hkBSGJvVyؘ8J`9QآȝӦ)x}5?d>BAp@0渲{nPsѯ3xok:;\R4>Ij]vmp;uvc B`O\2}ͺybs q\Hoɼ訯V ᆻn~)Կ`;/,py_ !Hwy6cΆlp h `4446Oh1d'783`4|?~?}jFҘ;7eR]+r!.+xYuAϟ?"QRRR( `@4 hD0  `@4 hD`Æ _< IENDB`cecilia5-5.4.1/doc-en/source/images/Onglet_MIDI.png000066400000000000000000000251411372272363700216700ustar00rootroot00000000000000PNG  IHDR^$~sBITOtEXtSoftwaregnome-screenshot> IDATxy|?gFH,|油oII BJvI}vM_ m 9^m҄@BiC \9lْ%-[f~zl˧liygϗ !dݾL#[ov(0Mٳ"{nnBqqo( @h DP f,n}Ij]ㅗ '  #MT>|e{V^MFp].;Ux<_ Dτ*[z~KYxao~s|#KL?Q~G/jno?eV?{8`ۦA_V7U!6+uq5_Oďd[aw}7qw=\ #x~LIΣ8.k `ICt&'=ڗ_g~"%O/UN/Zd w$Ao;5}r2zi0dddks-""~ѣ2lb!))imAAJ8aAMOr7|֭[)L7Q |-Lt8d`ezD|U*zq˲,=O||D"h4UUU;wXyڵkwf\\|pppd2,"BOH0 .,YAMMjt^}Ս7N2ǻ{:DkR0Çfx,vfzzF_O/o1DV%Zhe+b9szjN+++O7U#F)r{[r\ P**86~'T'EZe6!=z`0|կ~jժ_~9""gYfMXXؘgf퉉I}Ϸy~+ӱǶ~1otX,NJJ޶mjLޠ?߭&bo.=6u0đ7 ԣj]v堻k&8n,w׮]+WSO=uјnݺG_ l4[>V49fW1sĮ;v'y'OrwJ8iݺuiiir׃_o~D{n>yr:WRSS׭[wqDho4E$+BScCC'6"{MĐ{ .pOMx,ܹsYYY[ ===ǎ{K/3l],[-PA0F2Ju6RTTl Nt hE稬Z*"Ӡ?#gz$=O>_QT+W@4LF.'Mlgj"r'QsެPW͢޽x>{M̌+dɒ%q'LNN33+Rw"K-yBI c2+<j~~~))}S#_}cSl\Xw!)>H!~~~ JL F5[Óem5Wߴ,77^m7JD"͖/^LLL\b!dB'9 5߭~g Yv;!Z"Iv,X0JttD¹[jZ'Jɳmi_㻟/G.˲ =ᣂ!0␤DF'KL >}M.Et\T, \k3c  r1tLT.guU*&J;j%D`ҥ.ngeeǎ;rlv,X,nH4@\tu:&1'aY6̈"HJLrwx7〩H#4-QRu:ad@Bi2 @73LXbbAa`<___p8fbF*l6^j5VEd0L_ 3 ypJcߋl_`ɋ.NVЎ_F4i5Ff4u+93f /{rʙ3g!AAADeߏz"e !ͷ.XY_t:F#[ZZ8pwVD"???\. c8u'UaZC4kf X;ΟLI |KKغH#SS l`Lcjόhp\~~~lp|;7ou8jz$ m2a#ca~ BqYF9ݍɂJ:x ;6D㼹`0m6T*%Y4%1'9who PW9٬Fc;ax^ݷXyt֒ xidл^p8ݳܐr5ey~6nN\. pСT??(B*,Gwo}O?5ܰ {wyG,Oo1X{ɓ;v<)pw~BvsCC 0544( ,|ucD"xq7-&fƯW;k@Ecs7N0=ߍxd8{ @4( @h DP @4( @h ?f99svr}„2u\tohZtYM\g{0Iŋ.tpt ]μy5vՅ\)윹ohSt\R(f"7 ~ZQ'2E#o}6Dޠ?߭lo.=6#]ޠ>uĉ'O>wZUS}afEo榗ӏۣyK%ޮL?E[zxko_yRȈ!0 $sKl^C,C FSD!D056:pBa#D Pﲪ5a2U biof ? *u%)uԏaZp$*yB9LƵv=kB8D40r9i7f8V݇2oPѫVeG7*z(0&w~Rd%~A1kIr>(K䌍ݎ9+UszՒ0]!t7YxE68$)Q[\Sa%憈mÛ]`Hc4mvF%GT{F֨;fF"ۻu :w "}w5Qon"pbA Dy\Nu"EMWV\+Bzs>86vOl2h!F&Gi[r#L{vWWߟ!D < k Ir1br" r~黃һΡ,5yE|%DN}v=kcqqVxdu@4i5Ff4uxsewZ:* 1ÄԩtF1Z}wY6\ȘAm 2B8Y|fBT[[಻4W{ NΆ63CοVp1]aҌ425Ũ.=Ƅ)a^_zJϰbN?7@Xͫ }fϱTV_9WbgGYPar:%Ee+rϖX4jL_T&yG62O6yfiqEE/d :*)AX~Z!޽{r[o(`Ke?{qyO>j>\,90熱=yh3uK8IE]A4ةK D; CBd IiZNtBy#/L t/](S`rtd0( @h DP } 0.(l7㠰L:qP 8(lSf*lqPoj JTU)lG<[qvxTHؔ(G?Nc[z͝,#>**'֏.C=>l|olۨ/xbë# [jԬLeF4d 7hI~xNI(LH؄2owr=:[}(Lha;F.mkl1Q9#d oPшI[S_I L'Szwx\!m`;֒urZ/%X eNbp A#qLI%,Ytu\0L᳏7Vee3Kr`Q9 G:YQvu+45Lv+Sخ`frZ+tBn񼼕!z؈59ċn;ƮT.Vu`MW]R# !DRZut,$.T ! !ekːRwv:w}Qii7,C[/!}m jQK |cѺ-VtΖ 0tU h t %%vl;zfb.OLya;]pǽS؎f/x*_lnFa;s/pxkaGv3w/7P& P(l fv3 Aa{N(@4( @h 0㍪ FU^*/v1Dau0yO#3)l7d4Nh6&߬]uit]ޠ>u䩂Jj[}"M[_D}KY_E|PːV\ :ho4E$+BScCC޽SqK֛%g Wx(L)[uѵ<|:@eܯ5>^_zgFLP aB)nj>JMW];xX_ݩxnGl ? IDAT"GL;r-䙕DizrXNh`ki IXdMm-*fuإ(x$D*G}eZ[^0]!"U Jjec!I⚲,17D\тK}gA6j+J u6zA_ .,-7ZwA +R/(0A) ](iCڱ`[v~U#cAJSoMF$ltnΪ/>씥 d|=&DIk4T_!L@B51zAi~M 9eZ>O]"~1)LjegJNȘk#e .|tű*Q`]߽r,\ޖc : &{@9CnV!e\e}n0A|~KRB ]ěi45;e9+5N<.Zz 3[BP&917kk-NsIS31@4-.=,m~k ꥶ'.CK JښE/<$GUW&`LiSvJٙ~ g "tWo?ymCa( @h .Kp SAlHcC^nh G5}2wlJBe|v/w V}͏oܩri70Ejf9P( @h DP @4( @h DP ݻ?cYT IENDB`cecilia5-5.4.1/doc-en/source/images/Onglet_dossier.png000066400000000000000000000414731372272363700226240ustar00rootroot00000000000000PNG  IHDR^$~sBITOtEXtSoftwaregnome-screenshot> IDATxi`׹gfxM; 8 ;Z)6}ФI{4M6). &!1fy]ɶI̼d-}2ҙsgh  xSxo`kqu+L_?_^ضi:„DTWO'h 'NskſZ7Ҙ>>qo{Yo"~}{=cG '˸~-=?˲lVOK} AvOIq{ |k oR|v#. MB2?q-_ tukLI?޹kyJGg57_5o֢lX0 MOnk߭Y̅]q7-nxCnk^. Mj"_vbhc  }ҁ z߮'|ޓ ߝ.hQ'!mpbt%7Be ]vC&3-vw&xvapz6ņ{v&FKu~w熛{k%5U'֣,F>",Xa'7g7ϾvLk2zCavaVU[)z;?xp׳L{>Z۫w_%߷ZYrְZ ޅ[ x/#ly?]Gcg㝗;v8pG ! ! ! ! ! ! ! ٻ,B5s3f@o4XWG=欹xBtƍ+.8j@qhOl!L|4 @!00pҤI"h999mmm78hx.Z(444%%%// DYx_'O(k,hѢp@`6jj.]h4c"z`4`͇&IaFgŋok pppppp2,44/^FaRw.Xk[Balgaw}-Z??::::,Xn۶JMMik4B044֭[ׯ_߰ag}6*)**`h(?bqbb"EQϟat e]]_9;> hmx޿/L_WɴeALBB¦M~ߧwww;99uttUTT|+Vo~}ȑjq0nݺURR};wDGG ǏəEttEDDFN?{)%5}}X<)vy x<ɓt:]nn!SN*ec'>!ln!IM\#E2MkyqlQQQׯꩧX__oX?߻w\_rK/u֢"+dϞ=;wO+#3Ad x&+l4"Ir=Ν;ۓnݺgϞݻw0/_322٩7o޼bŊ8 bd2X,e٪y_.\(ɬ?WTU'#ɪo~Ec{}wD..h)/K:Yp!zG%D(C[rR S@U ݆M&;B.s iJ-A, }lL|9sM6lW^yeӦM~{{ț^h5\ hZhnniӋ-h < 5giGGǐ!Éml7:]pH_VV|pqq vttGˌki[D!)S.W䎦1I NJAs3T [ēgO R[ˆGpp|Q(`O \1cFjjs=wΝ;x Ș>}l6oܸq#[ /̲\A(FOPd6ЗgNw=g$IXs>;7 p?մ))oTvuv !>!2H&'5e<,_ybYvxaT"$E"Qhhhpp0IddE]6,̝;i׿599ĉ֙ζ63~Kj-KK'$rwj_a@ н#t߬s }?lsyASx́GNo܌sdp}?>|Ć |ɿeee>}L&3gXGٶmZ/m.]T*W,˩S|h+1͛7zi= 3͗o^uo+G9wZ_p歱X 0nR0LO?o ??uԃ477鶶>`Ν߬/JR77%K|G1qqq?>Qb:Xgggss@ ?~<&&ۛ$I_[ʲ?X3 k<<=[1 ֜8qb,&}DC+IZ,3$G$>WՀ_VrrX,{ 2z뭷?%''VtuuYh4F`0݂C ,8wO:z.oM$bY"6,_vwv^WFF"dw0L{{ٳg;;;׭[ܑ#GjjjvK/?ի r|R^tҨb8;;a5; r__/'lkl/OYI&77رƨ?hbG4 /4kRѕz+VL>}Æ "h4޸qѣOnkbY`+Ff{7M\\&G ^yyY^^ p5 wywơ??놬hH$}si EQ|>`0]ټgϞ+ 77׌injjjWFrCz= jǏ߶mۗ_~i0[j'|F..\HH Ff;//ȑ##o =0Db?>}zdd ˦&D1س=H!˭OzF3FÃaf$/^8w(B/xQ=&>zMLJ]a|>=-&jCḽ !4^a1jBW8p/2ppdfk@qh@qh@qh@qh@qh@qh@qh@qh@qh@qh@qh@qh@qh@qh@qh`ԩ:]n楔 )xw#~ ecv}iqnb(G"0É,:BlKuYEs0uT|rCmbԩz.Q_Gׁ+rQӣR_S5Mk6u;\(R|RSʈncMmM1y&Bh0uN|trUt;,:\DSN*e$i?DxGeӡW;(;|}^mmzKci^A "6V^'"o~3 ('BL_+M"ݤa^V:uZCP>yRFwzDqEP}=C/89^N"yӜ:*s uҩ Oemj%۠Y\*!LŝNٷ e8O]^SMq w *g8ZtVQ0 L$^2;u~*/kW6IbAd<;ʙ?zG7=8)qy--mƢU3x*v@#j02:P"M`VҪ{t:늶S@Pgw?Qh`M %yE3P.p,6ʃEVSm3M,s1=lNڑgWOgWOo_[͓>B~IE SRpfuh@h(MۇFoh7Imw FmB :`40@WyPqJxzjx#[Vվ2q_A>ح? h"<=GgZ +EJ[CFPEND%tCvsٕM]Ox !Nz{b|]G݉Jn%l{ѷN_%ɻW^-nyXyd ѷ`Q'C|M+$wRd7f[a!'-7Nz'[*$ԿSOZ`s{Exsɻ2o~\ɺH侥BlSuFP4d䏽HK*}vn pbZT2P+JZ6l#O'`TB[~gGN I_Z6'?9^̟)~XOOfTG,7zن_z_>)}[_]W[E<`L|3'h(C.'Lu!m^iO4&"  Y=>!:r-ڪ E9* FODʤb29XoPԴ  ZDpTB9.bi "D+網7%o:, |U @k˵1z L/kjY'u: }6u}DE3T޳{J2ӹ}Vv'Lk 3{ G!♲ke0hʕ5_{MNN|3L~wNx3Q;sկSJYWtf|OL.-ݴI?5 $VowI cx`t1ٹG+>ΉMs+۫͞߿cScǎ! ^#mJFzɓFݶ s=6>%O3x}C4ENAgg0?ں C[vb Ǐ-9!c$CyQapā\@Tf_Ћ%.Nbw6y4BtBhbF/P UT[X驚Q}qRSAd_Y_X5eP|}Vu}vݭzFk[dMZ=bZLѓgG9g@G\%>ϻXt1zf5ZE>ѮB}DW Fv4RW C3*R\Z8|cSSNUk헼0![{D˜ hyS6)pvtvqY"dۄtiaث?)64$"7G4譤}ennP@p~zߕϴYWmߕ4Z"7I:lJ+Hj3BZ.eJoVeqW-KX;Ów6P')/ZzoNz3)&b՛VDIn wCN+Z o&*AN5UIj  (wj/E?:K+4Hy]7?.d]irRzP1Jz\`[gh`ty2^$ztɥS ;81-Uw *E(%-6E0}*k-?ݳ#o$FܯBm-]肓M\OoXݧ'3#qmulCTc=/ 㭯 "pwS&4I/[' ]DDC!+g'd^GnڝU[UD(ZXry6Z%Z|M]X@Xy2'*0f~ȁpLK^ 2\q<90-g}UaaXZZ[P`z'\הV{Z783G'_)T̶#pY4ofg/@ydMC̘x1o?ﲢa"G .WF&X:2-=!\WX^)\8S%@>-gޔ `NzI[gmuo qonAp烩3rc_XnL',+TNRm!c67IE[o0[ҳʬn(TlEJ.^n2_*b7t[ (Pxj58;F)5 OzFy=((L"t|s6jdg21BϏ(':Iql;u2,ql?F LmXsRs M:[%|(#&>o+׆P&Wtn߻ ڀ>/L:^&HQm6;Fx{?+̇겹9!reG^4vLw>ߝaLTu`>8d@cn.q_lz1enmMϯduEFBGW[>|VFBc"&zN؁"ɐK":a \BGV:tpuNijk:3lahs;VYq BFBFBFBFBFBFB L:U]7k#'N=p&RS2SN*6!lT/)+ؓ,֦d6VE#`MyՆ.pt BcVHY@iЧ C.wP7Yh\SGWmanhቼâN0uj@}>͍b!'.)8І;BC TDE%|JK1S=?S5ߩ"6p0)SƩ*֤-,2M7ͩ";P'd<;ʙ߿ޡBhԦ_%dADEݺ=MbYV*RD y V5ܖ5ZE>wKB_̙o^"=i~~+@ny,a{VEQe9)vwm.ѷw3t*J.cU-4io *b2T4ܨOUH[(vGt 8g O޸G "^SԦPN9N~aH~ :}< 1+H\j_Ua9+4[H )Dh4p<!;pmEqh@qh@qh@qB?@agRJrgrohk=KZs(?*[%'khf#H2m>98l: Bg/T4.0% g.j֬]7laƏ[^^4}(lq:Ԗ "€31L4Еgnd2T%v1k3COC\ /r|DcJW&V> -B?9 W>jPg^)j7OPWZ"7I:lJ+Hj3BZ.eJoVeqW-KX;Ów6P')/Zz+k.>ۗٴn+(,nfZ]䕢fkͫ%x&z8ɻ2o~\ɺH侥=d5 r=i{˴%NU&ĴTI+d@WlظGNT uˇq swm N~r6*r?usYH`곾ׇ?ٔg[] DGٰp6'>9Q'[Á6wۃhh2dLX:^Ms\j*@OatDD"b2/W.iM]8:Aϋ9QJA"Qм\;`xbC$B[d dTKVfY7@wYfeAmfņH|Ix" \#ý\=,ot"l2‰e\ƋInyu'ekL=0ۣFa#Yj@[z:Cx9@Sp(-JW%}pɞyS$el~D9Ҙ] ~?CЃ>3>߯PSA8J#WN#¢lFSD<ۓ b AդgY]z9`nd`_⟇KT]~=<<֣!V1SA y :'(sPVCϏu2X $YNCKǝ)?t˰|gGh`js5tZE9ܧBNqI0@ɳOf-`ʵ!v%@L[Z0M<=`s)rWaH'L j/^L[s+0f]->3ВWtӒ'G<'|.s0:<;ye#dJ/^u֘z;v Q6F w+i#dTABFB㇦i`4 4~tuA×Kn5aa4 4~N?,.bR焌( X$c0QՇ\vݺSƿuC [#\!j{fpxBрррррр0v]x!E]7ˮ1uQyBQmHuy=@P}_d[JZT1ރ SN*e8TBh8\SSxJ-NP[:AbPgW1i؎;[J]naJ/!@;gTnz@XjKQNrzEHNNWѣ9jh;pO!ڐxA1B;;bW*>B~IE SRpfuh@h(MۇFo/ u&8N&C{$2^{9׍˾=-Iݵ7ywO8ysN`XQDVTN hzjx$睋!K񔉻m0[ô|e⛉P}ħGؗ_톷\FXǏTܗ!h r(K٫-<7,!;_qVMϴeVMqNz;S4OPYByX&+E̵ͤWEK/,m&\CcQ\ѯ-j{YIdvF8'>ۗٴUo$*y\MXIo/]R۸;QI; xnA׮1Kd$gW+Hj3»cgm5|  x7Z8QWgB*jYxqG<7TFVXݶ>.OCF؋֒mߵ.8edͯONQvSՑmݕ@ 7n}m[p⓳ՑKgn󮦒o?J/R}bmTۗrNsYH`곾ׇ?ٔo}j芒 wӉ">{_'o.nd@UҩJ߄[;i"2:zEĖbr(非:/F/=/>r֌bF=@D@kaD[(z_+CD'"~m<+6D" WmehmV!HHR|Xg4+6D*SPѱ!R!@dbn &d,6G2{7,[*2,SBw\MZDp/J1y |7(jU>ry+FEQ1D˼ŇeKLgx9@mJEhA4-Uw13yWƌ]\}ܟ$Hػtuw&$l݊eտ6Ww)m "&Fqf9dHc^VPp܌ ! 2{UzWOw\WX^)\8S%@}S7Lw<;4 =ߝaL#Gf:nwivУ+ {t7<=n]7_2wcUcZq+lSu ad88VyFw~ox5Q-fbD{nڿޏ kLr;(p[+J{ǿ1Ķ5Q-"Y便и۫?4MStvv񣫭 zt8 u[ seq='d@dH%_ϰqV:fuK4Mk0G4عBP,8!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ā Ѓ ЃO(B0B0B0B0B0B0B0B0BxJrgrƿmTvh@h9hg: Ba/T4.0% g.j֬]g& ibPV%0FBdB :`4F%}{IDATB$J Ӑ=xs#J\ߩ[MޣB3U ]jv!dZ1]h@h҈ ˭ojYkN%5q>2h}?f3JW)U}BV/u9[ŎJFgSK#Mp)ॶI}$%QNve\'t1׳b_1~ޣtLc4 4Xs{PLpK8H+kLFQLІk)[ׅ8@8J}e]Ad'M4US[=ׯ*r7ߨ7vt8)GGg+ t}Aᱢ+g|KJ: -wnwvʹ`4 `Yi"Vl´?k`7o|vv'd"(M[yƘuEgA?٧lؑ5 8`4 8`4 4h > ]m]!׭F[kgaÉǖEL E!˗D~u?vnW(jСk֮[tNӴV[{;7h@hjfpxBрррррррррррррррB!;?p8`IENDB`cecilia5-5.4.1/doc-en/source/images/Onglet_export.png000066400000000000000000000220261372272363700224660ustar00rootroot00000000000000PNG  IHDR^$~sBITOtEXtSoftwaregnome-screenshot> IDATxy\Sw9Y$ WԺRjmjkKoSftyi;3նN;Xmꭎ"bF YB $Gk>!?B:`!k֬0`ٹsG}ۡ vZBɓ'n00(\xq4HP @4 Y[Ol+g$H Ԑߑ~3O;%ձ4B(* \c]zlݏqkMF= ~S<ۏo>eKʖ-ҶH٣~U=p?qWr7q|ԍv{6<; ljh?BSpوm/ΝY,xg WUG_X0X(K;2*у<'zOQeێ<~#Da] : Ξ5~B~i?zuٟZvj?n[LG xw-4M_1wn篶*ot:ػ/WX큿he=pNkr.^:eVOO?BG+>:ءW캏6{2N|לvxغ/2]>,nYS;~8|K'EF-_O^?BGn;xޕo.4d_}^G}/]vΝ5HP @4HPܿdgkɓ&M"-ip<̹@p@T(rrr|8xP}|u>eJJ05p8|1:x! も?t8MϏHRCCO JMMM19sEDDbVVV~'OaPAёKNl6]ps E"AhhZ$U([ѣG?2VT) X0 f͚uܾ}ۋ;i\`I&M8Q&Y,l6{qGxBe[>EFVVlU,;56 B;kF<+W0bȠַ3 #JRJVT11_|ʼn'SziӦر 6lPQQѷQ̻;9s[z^CCCHH̙3yUVm޼933[b'0_pk, ЛzhyV ฾~yVX$8mppBxcSze˖͟?m^z5kl޼cFyƍrKhh~h~_z)!dŊ˗/O>p8,Kqqݻ,XVѤ{kg1B@;!uu#tAH_,V|!>>%zɌ*QcfM:}P(;vϯyyy a0iii]tgϞ>#W?ZnN۽{ .|W^L;۷owr߿aÆUV-\+{O. BHŲb9RY0DM@0,$0s=9 {߿xKo}ӱxKxE߻v@(lٲgCSQq ѻKppF]f/_znlwuW^b,wճfj?_Lش4Fer^۸qcQQQ6??ǎ6ly7?Z^. ttra泬{iiio zy^ZͲ,!dS?KzܷdǏgbɩZgz"Ex,ˮ\III\~j=t/e˖M61 O<`n}ccc^oV\`8D@`m{6q.#bqZq',=7{lJ>;#M+_b/Dke_\.Wˍjz֬H8* ?´1a8.sc 5TN\ggeJ#Ŧ*#@qĐa z^GPDiӦM0-׭[|򨨨?Vﻞ3g;.ᄐrBHmm_v[\Ι3?  GH$ 0Ez/::ef?l&GM~}n董"!D&EEE56bi^ W2 v bb QLq^7.DDǧo(r"x|UNQ.!pu5SD,ጺ,zvof%oC#L&'[={622r̙oZL4)33^h=mozs%dggO8;1H6,R 6 h͞ y:T"Gin'_>p[$`ƍFnݺܾsOH1p (48էkk O' CZ2* B KgIE6GcBU-7rWYYܵ?YµIX0ªq8?u6LB$ؘwByXLF_ a$܉"^`t\~~~l9iҤW_}ƍ'Ov8:|'+ݾi&P㏷fyɒ%~~~gxt!!!UU,N&skKV vVh4Ԙl6D"!N}D9Xc>zKLn65G*w@a5L5HC/*mڼ/)͓O|)H8d6!R\GĪV 'ϊzp8mΝ;vؾ}_|A}bٲe7nxG>۷ooذ>xJJMM%>|.ݛoj*BHff_Jvuu6aܪ*׿HkO^֛eqlʪܟ>lƬv`}Çd2ǏߵkWFFFmmmwmXm۶~JepppZZځfsjjꫯm6cڑβNT~mJJFaYV.7g悻_)ɾxm ~qe}] k?=K1e?󚚚_znzšC ￿iӦ Bo~BJ6߼y:䭝 ]3LYYY!!QQфب}_٧~`yS]ZZĢ8n:uꔇ+҃46qVȑ#/n}ey󼾻M6+ǎ;sLUUZ^dɼyJoqIﷅ`el߾}111~~5+~Y\Vj""o>S^^o79(q@љhZEޕhѢ L8qٲerl6ٳŋyw٭oquvNaoq[n~8jTKL66.ܹ{p= fC; n;3;X,w޽{w\~,_G\2oa˖-+V1cFxxxppp].WMMuyyyv/fq0h=ixTr\N^,!IBI?*njjO?bDHgn} fH`T|B>yn89 ø9īTJi7-[xZpZPPPr0qW[[+R*Kvq i` 0^B |?- O8N$?z q5$ 1i^ZnΝp">2ًk 5HP @4HP @4HP @4HP!aHh4c)2e͖+/7C4ԇ XD8dw]P@%_Tk75u 6 W#$B"!?c2sB4"J2`"FKa'mU7k.'/GRHD !2K|=מ/'0Aq&8Aۨ)i𔯽s?.E,*.ؐS5N}o ?8 H_95=XiW"$Zz`up&SInP2r\^b9mefEPY'x NNYkkl{+d rrёxwj]O{uEZ"VǍm,(1%Vma&6>jP0tlȨ+Kyi@A!2,;QīpǸKS秐׍e.%MBȚ5kgl[t2Gݠ^0#"`޽=J@rOѣ0ۆx=y i3[AlzA6H1JQTP0$X*Q\8ϝ97>!Azܙ'_zd0(@i (@5@`a! AvC,l Q^na {KWxa0!/,lǰBq@Pxt\0)sN>v +(BGGE(_3yEhDǒ4"4-^!r}pvF_؎sԿQ~wkqڲk׍aL q{Kݕ7/uU8| ibtPn6SALL#Fi+Zo.I`K!j攑azќ2[0U^Ao 5k`X0Rz֒k~ b=A[#U8o?Uca3Vw3vlZ]lwJ)&KoUvꪭů^,2=YbnWsyz~oO7j>6/1"@`7_ݣ_"m?0d {vC-l4ǡϽjvпaa! @vCT8 (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (@i (0 ːIDAT@i (@i (@i ([nz0 !QQQ .8 (@i (@i sj=LF-~e~Np{` $''˫e\sqwF~yO>ap@0~fɡnW |!.Y\uDSO/!H@רv VkTn4  H$P-7mWރӐ0 12Zsz@''ϖRS=.$/ߋJʬ]wyaHH eeeBos殝ڶIg_i ` \.[4 }qߎ!rxpYy# o=?&2'sFDZ7zC0t:'zŋ1nr+>dիW=|0@t:s8 (@i (@i (@i (@k֬rYA IENDB`cecilia5-5.4.1/doc-en/source/images/Onglet_haut-parleur.png000066400000000000000000000411251372272363700235570ustar00rootroot00000000000000PNG  IHDR^$~sBITOtEXtSoftwaregnome-screenshot> IDATxyt?gf,[$1qo @Ph/lJ+/B7h{J@ ;8&/Ͳd->3?EFlK|^Ԍfyx>zB'\n7xvDvNέx//\O^`IҰxwG|4,"+B<5K.^?[8ή5֞[=_Jâ~Ky?ܾ.`ͼ B<5p\{~=ʘDf1 ܀4,lk2}~ ~&{NOv6=?}_5p\™bßizro?z? o?x_R '^}=z?|vq}G_zc]=m3|uG!gqm?>BKy~;p_bpUs< kBdڛ7o"Q,H{fG}!Ձ-k0L!<0C@{ķ<<2+yN+?~ZwQq"ڍѥ7 ;gA!+-Dw{{z>5^1bҰhy\g6qGu?97{<:]m;K?g~}M\svyiW"BV/_xv裏.gu,ky tSO=oJ( P 4@i0rI֪Un4YOu  ˗F5-R  4,' @ _Km޼Tp8;48qD{{{2ZK ^3 u!HPp8yw L=]DQjw1wqX?C?OϜ9r:}}}Ν;}%那zЕ+Wx XYYc}.k>g>HYYd _8z㰉BL,))\q-6Xa^Ғw254*7U+qqضm[{{{zݫhm.w' tiQ[afwO?~xD"yW$y{ߵkWBZY,V5LO?/M6 -[222 |ɺz?Koλu7_onpUPPwSm#LǛMnŨ>B'qiw$Y,Vƶݥz 7㪀RT;vE"w3t9d_;~xbX,bl_|ő#G8@ x饗۽^m۶P??^9ES{C}P(*$--x۶mG[ӐfgWToQ̳5 OMx8B<XSv!c./]rߔ::=, wuh]vz5?G}tΝ8rbN@@ @д YٴiB17U|&[O=^Uej1,v$Y#( r#<Gv}g}v7=)deeٜinhiy\.pٴ5>o㿬8ٜɢ ;lIU Qiard\WtJ;v&wU{"5kUJÂㅄC8Db͛7_p!,c&z+I>7g}'\̂v:Ag.P(&`q8MLmVLQ99 A{Lzj>alF!LBm.tc.5JìphDСyBӴRtW^]lxjUՉhEQ*r$IzF1x sp|`6YhX C~?^rU~vB<4vʭr!ȑs׍\.0hBH&v\k`hiB9Q )Su8YmlBH@'@|~_׌O%!7Lgya"&o,kxxw}wacs8$il6O6bZ݀ tTV9]QTʜ(jxxxnMhe&=!Sett]9EMgOs"̇BWU\dmn:ӇK4b /b>sqM6}lqRp B$aJwE"QLFIwdX^/EmYsY醋l=tMqa޾q3o⺏Z-P ^[BHYZ9qPī¬ʵY( K\*S^Ԙ `9R @pc? }; m1_4ʡC Ŗ-[By*ݏ}=Ǐ/mqRq2blvYfs;l)*|.t!tgNCEzxp%]+eF mN[j|t2PñnO^ pLo]Ht:_yg}nw .J۶mOp8loW^gn(Ékj5㢴>}jSZ /]W~Y܊(nO>IF]iX::9qlkKn#JKT[}X,/ /=z!!P(FFFL&m/꫟}Yo4JRiZ'O B%EyYG|)]$ij)wNJ[:uj&K`q͢4$~DbXǰ:7ȟbl87z~Ç={l6+ǎR?'@  '*>b>_VBʌbH$k|:K/D2R|hlooOƟ%JRDQ(ǎdpX&9/&| %,~iX K]l6͞qx.[, L`K! ljISaE`xgx㍩p<2]z \( P 4@i0`J( P 4@i0`φ)MVW,yNjaw8ثAisVܺsתw%-p$!)uǮpBfŊQCB]H4kW]|'N1ds E%ð^Ymm̄0,iR=ۏ ą@ -+/O^mQ!BՍ5rgd🧚9(Ddy +S)=&&P#ۿb+ ~>ʹ_ i"k/V&$96UU!)F{30lMyѶe, si VD$' fR͉נrg֬RiAHZRkKW׌pX豸h$DQ6jM#dDqnݘ@!zL7Uq|.j).ʪqYJ;vfUY9r ° X"=[<8W*CzJUNPũ7uF" GNkP}$ɓN>lPi>UgFgZ'1Cb1b|˕aL4S{F 4ⵚOB>'+6LSQv'`!"aiYޥS xUiiJ7|`!}Ÿ|.}?-0<ÿ^ppa/qm4z+ںR14f$`D2C7`ցiI'_reh5 G:L*tDp|FjIU| !\, v\K!-qxye/X8 MHDmY8 W\M8E{.o^l%95H:voh4Mk6|BO>drnp{9346gTq{]u`rzp6 KܐÇs] J;1,^i(Esι9c2.\.H=R.aw8c]seRv:Ǹ CZR\$m?qv`%Zj hS:h> .ildEbf˫x%ڝg>q.+K](SAN>2hbi겚RL'XYhdF %j{Ai76i[:ƛ);#-O_d5sQ=+珴n,W\)ZSqgcu那͍rpLV8ÄJgz4DE?8_=ln}CFͭ|huQס6B( opdBqfvQFA4 IDATfE:dCuYYhw"Nv3 K;T]GE @&84j4 M:sW i"2 o8Zs.oZ3mhǃu^>{b+bn fl޴b2vz; \MADHf|܌Kv:[zp-Wۈ*' xT-l^~t]BB(. L+Q|xmQϡv#y+;[a<ػ('qJ } !\RW`{(7Mjݮ^0V nD'6%؎黕vm) $Df7(S&ctܦ|[.jj[1=4p"N2hHG8;G>}FdO7=SmnSǥǎL51}@nѤj=t^/+d=GQv?mRg!dyd U(}e{lPrf.|ƞ!P(„5=^0q/vM '@9L(wcCHWR1$Ծv#npie!aHNHnߵ]. >NCZ ¸g314jtKD沌ш;dػ瑐DfRJbfp4L"Avve#&HiUu( B@K\Yx1`le&kvB)ۿOO!D;.^mXQЗUI:jQsyUkKp+ZNZ:3Fuж.L!bʲqZ!\YcoetӃ(ð3$ۯb4|BQV -nKU+)9!`I w4w,m?кhLT^6O wޢhy,t F ]PvRv .EAH.K9lۥ nTpB`J( P 4} `^ .@H:K9lۥ RćE 7gܢ{9f5ZǼgddn"a'MWV'H/mEnA _tR_cynΜo4F!H\TVVH  25[1z_0mnsᱟ@ϧ?\^v{n\Sc+'}%+P9G-^|@f1 VeҀ˲.Œ4P,Wgzێjv782B݉nF:.\qUPMUP&h I9⭬ZS!d ;ڠZjr)ym>ɧu+KIŋ41q@{!lt@(K7!sS^Qw#V.<krfqxNjשl61yy`rUI: ^swkn#iFb&rW cD6GFpܲQ4ಜB}'tD$\Pbm^XR(p]0&UgMnD"QFq惇FNkP@|ƃ+uJeBY:[#r0,=0 Jy,pyr]< Bƈ8s ӏp"2턊,'d=:2ܭ˪jnjas(<.L4 ^kIQp鑙#u!nZQ] LSvb݁yHb8#Rt!ML_Q"[H xlZ>|eG#,^"s<؎%QrBQ~ xBvnZFcO6.Z.ݵ~B)kN c s59?Jiw`l@TSp(3?-l ۥ0`; .EAH.KQl Rہv)`P@i0`J( p_K9lRہv)B`0vsrl`l9f5ZǼgdd'-<<<%| w`ҢU߬r+e!硺 !R0nM+*.c'{Q%.؎v]t*+j+|tۆteuCJ0—0NfⱫc!Ddy@|ElUv-79+v˕2CȲe/N|% WwGŃe-/QW`e!)PTxxk1bB`,J!6{h$"z"];鏹[b$&Ⱥ.flU%s!ƒej_q#@;[ ̬CL ,&IeyMbI@(+U 7{0 rŕW2ݯ Qv'kE`!}EhS 㻠c/.˜{g3 İ$/ol` \1cBpBIq^:fs:F:/T*IgE˭dt\.ϡoT%{1,oZiMG!B]Y+ z$Zs#Sy*$]pP)e5+ 38ߘbt)nzW5[2ݟbQf[X|ydGa\2=<C6EB1.3fڨ '+SgQ^<r7b*g#U7` \:8K0岱nczGt=VAYùܦ+ZJfDF{ q3AD֞4-_l%Q+BfLx٦,4Bq!A0.s YY=W;mH2 *B\p0sᔍ+UsL1pQ`\UFUkVe2$<#o4⚼xP#'.aR}V|AsdJB0%l R-..޸vptʁ`;\l $ۥ Rہv7*80`J( P 0/lr $ۥ Rہv)j -dD%R\&ph{ ͧKUI7͑%t B%.Md%}<j/_]8ㅇG-EB$4nɯDdj>9XNjsepg{ `<2Xn Oc(.[Vn!zjנŋj.hYU IKnZ+!EMM!F#f~|XTc  ޠ4oc2q,$ۡ(O(&,ʪ5B6VM\f6Cن h P<5^˹kʩQq~$?%OѫveUjeFb }HmhØLӔ!Iv'd*dY\Pbm^XR|r<,g<jdSAx.:"3ٸ?VCc&TԅcMɌ|,aI AURJRŋ)) StnbvӶ\m#vK)؎vm59RBT8ܙ|zMJi vK #؎sCNm,e?rivw@ݒv:{* n0HഎwEڬNH8^f ػpld@0*GlѶڼ$MzmZ$Gŏ3b女`%<8ʊd6˔f ۤػl@"Y`vlnKv) @rA]`;\lr ,K9lݨJ( P 4@i0`J( P 4@i0`J( P 4@i0`J( P X`ia! N( 4@i0`J( P Xnջ7K$b[>8ׇBo=ѶIZ IOoY{H[wyB>}qnVZG*E+Ci`,xaZD(vku"WiHֲ Œ4jL{fw{Yj6 }h\OPXl pCc4Bc c!4u*߳E@ΫǛyiέjLJ Ssr5,W/=tB:y^X#ծ0sٚl%!/!<[3Ggfe]:A4p%BU'y:Yl~KIDATc ÜߺH!L W}*Oe7',6~֜CF?5@{l.aNev!Dwh?XkMm/]='\!%]j:leiXgCT\h8?rO!gF#2r?$tZז~MOA&OPXc-'/ YBɄBvva/q@Ⱥ}z#k0`@$/6@i`! \!?;}Hgse( ,O>-e3e@xQ^]?p Bss=_;/|$It?k׮Ź Hsssf\tpB`J( P 4@i0`J( P 4@i@|M5VIENDB`cecilia5-5.4.1/doc-en/source/images/OpenSoundControl.png000066400000000000000000000213411372272363700231070ustar00rootroot00000000000000PNG  IHDR@m'sBITO IDATxg@ +H b%$Jް+c41{oQQ1vD.K/wp}yqwD 32_vnow[`  AAA: xzzR>999}P{@|8H@1H@1HyqzL7n8;}Մ_]z#d3xYtoֆ }uJm=6n@qӋ)WBY|.x =m{X//m .R69n=UieZmߤO[5igƇa;̝sj8I#yd;F>{byߍ[? m}u4r$nq5vY~1cpN{V+5a=fp͍%I$@uᘻo2yI=M-lqJ7n{?pdETǴkՅ KZ'0:[R\yɶe2LȺ}i`3y̡LY/ot[قi}\wdb):΋$ /o]wMO펴}q1W׆YD5@9:0|:m/FwmPi\o\\M{-᳆^js_8hkff!)||T&]h"ʻd?ԙx~pXg}-Q}p,6lwfdz#YL0bFN@iɥH߆t8oɕc&oS*cC)dƉ]lNJ_p}rԾͷVbB)GW { 8>10mSNխRIc+Ff'љz, pmqNۥ}x>j7 GVȤ'9w `RR)oR>i n,g=aXL^^?R5L>r\QM66kt%rk$PuzR8sK $`5GZUPiCj)ULCZf @0?RW&:[d@Pi8)JdZZz dٳn6LJAV4d( Xނ^$Tn)hj.T9FՑ5ʭ [ oU\?XP @mv;14"p]^3>t5Lv]h$Y>&:p$~+=9G=YNZcJ>mAqk>8Pt0{Wp9Y2wmhq3'1P@RW$xg&3 xBC*}דI-t>aO7̈ @aGf̾*X"f)(Vrj5Gc?yk*wDM¢+Oi>ry;8ʩOc/[w%K I)l#*Lnzx'zekJmܕ YAyY L8-o0I*:dm(Mt}V֙˲oyjD$.eCf]l oI6o՛8* ϧ]I۳yiZEq&zsb_w_[‚ٸC뷞RXϼˆW7M ib(>})5FM[Ubb$#΅~ ]Pc5 Pc5 PcBp@ '''GGG M@/h@1H@1H@1H@1H@1HʌK,w@ *9x` {Pc:WNZ ¤&@tz&`|i\7W׉ #}f}:q/Skɶ޹h{D ]Nlmr@ m C?u_++`+}%K؆U#fmqNc5zDm5-Lpw231bN/|q;mpkc gȘVKb$=۰&K˶e@R5u/*7{+|~m '/Qwi)n3=(C|Ma;iSgi} ADMW`B, LWPTR01xɭUJ1:尨$?!,zq֭3OSCodeh=L *"]@etH omp=uk9u3{3SΝnJUtG:v <꾻S6ؓ#*hiF,)犈ꔇ2@Fi!NXz^1'_wiV7C(!i;sz0F,RgSMuh v̹#mu C'/Ket.qq> yw +CKB|6NFE'mRi8͟?`'NoS$|qݍ R"QXVKt .WavnpQ?oscuKGDRYN1m"6ie~0!p錣LDv'#5qvWz 5PTĥҳWì gVzPd2ff-84FT /НUk:lߊ=;MYZ iZI-r¤p5=8JPtDy]9.&ϱgZtS6l9}\?(=MvcYk|2 *^aìO ݉WA2͑2mWħ5j8tS'ɿQ$m.mT̈́|!lG-P6CeqӌÕK>>@b^Kb+0{TkW!ztno^jwzwj+}3MʼnlGcmW9܇PҢǎGT|Gw:PjM\ѵW޽ʏ.Yצ ;(EdQ}@DJKb(0JʊRfdw$<3Mv8]l@eܺptWI]g6o]ȱ[]-йZdҥc7e˶*-iX0^Aƽٔ~4PѲjfpwb*;Xex 9&3/&\0ֆ)^pݾC/4,kjb/sG.&(ޅ}穞d H<&U)Zp߸ؾwe _!deb8-R0M,ZCǵfja>jނo,ʼ,!/% .>]`'CFT^>reiBb 5$e sw,`&VV\Z'kſxQMY_&jTa+:'ʼG#|u>KJ-{\t:ϱ1[o*VѪ ڎ?=sx wu3J@E/2s[7y;2dPot}')/5Rm?sd瘝vEe/{!+xeiio{IIS'uMR9*G!"a~Y.(Z]HY/L*"$fYd飰k 'mZi@1PR/bw{YzC~\5>9R!S(<-n統ՊYŒ?~~fгg?nv:\Oa~BJnxEX0~]**1h.V3Sao )*L*@,0u8HN5ДET]mMJ8[OCZɗ6k$9Qo ZcIϢN2 bRR^Llv.+y]Y)Z-*}bv%Yz%O* ̉N`(E1u5`[9re5:l2K1SRur!ۀ^W/Z0)*1u9bLmMWRKԨ[gM;Svc`ْ.ª?c\b8AZ`TAkDLQ9QY$g&ÀnݜaSlK_IklHZ*Q94A`W!sz8 m4]u^X ~K4UY)( ZT1fr(4L,mxVnb8MU LM_[=-3k\ʪj!> ^URA} a#\Sv, Ք˶NҢUD8#SZoPCU =Q}݂FVJ1Qdl \5 L e}yW+ |2n[w@,(|w'E^fꔋ!*;EJnYFI"μ{XN_L||+L,+ZP!,+.+ytVVV1B$SU"ӊSƳo0~j\b*s`ˏl_:{N,NUC7JgC5ӿ^m[)c%c.`-svCISH >sa% Zc/Hꅎ~F -aeIhye2 JWNZ ¤&&Źw~ -"]0u _WXS^<2gr=U.(|*V 9 MlA|i\7W׉ ?#5;mPSQc%^^'U.^SF⢈W~ZZ3&F7{ѣ mߜw#GP=AXCt &`Iz}xrf] 9L-]MsX90&kLU+-{q;mpkc g2)ʺxî c TX*r/j)kJŅ/b6ܝLFLp3 ǤGcw%$/!,r=IadWWh8_I@l[8}ѾUKOquu}AFW8oׅwI󯭞u>K L;5}}6n6SMݽjlӂMY}xF ;ʤ"~}Uka/QR01xy_wZf@R bHq6\.EŀˀBp*-Tr6 a9#nyʘrdGS|t{@TDTρԙڶ-{,8`rQgFS;ݔu"y$<}w5,m_jL,hkMI>qAɻk.M>y%%$i!'vZ6yü~ZRAقX:?i f˒z~NyY.P/cͺmuX~vju3Ċʘ>7iEN@G^}3)vl4qu;zܑ6O{铗2xϸ8Y;¡%!>nr'/mq_G|  iX0OPHᛋkM5@ mR"јQG, _* ai8͟?`'No+b#Q*36n8>sn nh +ݦTW 8Dd~20 ;Xc mq'}GTƤT !WܦbJSQQd/MTKwf EV/3p}SQz@W*  xwЗ]?]6fe`Yy$^OΪ5}{oŞ|&wqCVRK0)\} @#>lR9Qm^WN챋IsV'YDUSa 鏮:6w(777DHAy+P+;@7GoO(GYނ"s$/JfQϲz$*^aì'Hw':\-!06GEQTf K-U$@7>L'ɇՕO^IϏKwn83 "ԘfbAME+BYf:p> 3[{kxRRVxGy=5,BWը|gAE t=w(B8}Y5za>{⪡T ;F6\]^V|{nF7;Nݻt jSILx:8(fPpGbB^_Wq!Zf2Yh2Ӈ1PAhYhtc\iӧxгF tF $`BAF $`BAF $`BAF $`BAF ,4 {zz,Q)8iPVKnjJ%hd|rCa ێUs #j 0 #j 0kw>ӿXbq')üܡZ&Jp>=U͉oP pP,ܜO?N8#:\N&v^kb #:gƐ*O!#o5a1KR/?,&mOtMIth\Pcp; HQIb av5I@t#SK^QyB)ƵsOUkiHNL'>-Q`4p؋ɢ_? XZTAX~&oiE˙|$ 0"-.}Y©< `CE©{<9!Au\V^)o :=V$bb7cQAjqIDATW@R۲_DcvvY  E|Sa9=ZäiIHIHQI3 #k%$UǘK$4[Ѩ&aMl`O-ۊ]jPTn.&2Y  JޔGYڦ&&1,âWC3)okd8BʪK啩7a$IfH1MK3S}KFu2*brkx2 R])(6ԶH(+{_I»aʜJVH0xZ¨䬑gw Q\X|ltJ޳z0¼Av_wp}OFR}]5 dSK}g1ϰfP7.I^2_4/iH !'Ho>{ظi󧍛ŷ/C{A<3)(0Qr>/M \Q@;`{ɢ+OHB6 Tm6R㎧o$ #j 0Ӣ6TxucFhq3'cN^1R PcPc5 Pc5 Pc5 Pc5G\)IENDB`cecilia5-5.4.1/doc-en/source/images/Output.png000066400000000000000000000163051372272363700211400ustar00rootroot00000000000000PNG  IHDREvsBITO}IDATxi@Tg2/&nV^%\^-+Ӵ3sɥԔ.EEEMaϜag#3gw󟗳>Ê aTJ@zzzKA k6 _0 .H(#"0 @%tBH0( % ?vCnd {/[?1o .?A'g$A+ͧ L:KOʮju*1 .8ݸy! <ãN6>ݖfc3'.8஝^ TQYL?}=6Բ//"M Y{1VN6~HZyI!ILH̺䯹7@Yڙj .VE^*lA۸HC}@)+U~B \;尰ݼ}w PWamn4" a<1ҚꞢj+Tqm'-?_\Q xچ" QZfEq*&;o잓n(T@@ЀүOl J̮24slQ ~to>?{lkoIg̘1111FqdX 7n8x@ (,V"o/#)U/g*Aa+Ey"Dpx/F{"5¹BLq\Q{bC?zo;1"nwVshL;vXjT䐐$럏=z۷/_lq,kΜ9b $N:EFFvСfҥv['(1?9ʫ&\}(]1^e'So$\b-`>.ijgb,VΜ2=&+sj  5(T-|:tԩSYYT*DǏV(?D";p]vd2 ˗/'I9r܆p7AuAe}a\--7WfL+EUe&o]nX` x,=`"7l2TjՑ,!bm?)O՘a[Wmh zlnY^~޽{nnnr<<<<>>^,'$$H$NJJؽ{wJ x#KGmhÖTחo%ݼ8jPJ«S[QڭES_(LUwj#<ŝ{{K⸌Kz?4&\SP@DQnyn͇"cjDDDTT%$$؝m .\p^v|- nC[{jX}v~{7s7r>n7s( @qf1s|0ց|ł1 ;y򤯯ov233 [`&L󳲲c HJ:tPéZodddxx8EQ>zs`ԨQxk׊-ƗfGEED~-[ԏYYYv[Μ=pp88Θ1T 4ɓ'&$$|Ww4hдizm"QoH$R>߶mۉ'N>ݑfyqgll*Ⱦ}VhxӦM}􉍍3f#bAAAl6g=z0Ki[l1իW^65##ڵk'.._~BL&Y9r]ۂFń U*t}۷/́jm2>=TjgSuQPpFBE a$\PpFBE a$\eޖm۶m۶{y_ma< !M`b^0=}[=بL^}~Ls4wv;ď0Uyx S@yK eěNq6ʂ_;僯\T-Kׄe*ᡵ~ vן'sb ַᫌQ /9xE]بoJ+2Nyg1jL,f+o1эdE?ON]Əϭgl_ن=_ g IݗmUV]#"φzr,V5aij5uiÃ.9tyػ]uO؞# F -~367#[>U8wxaJxuJR`"EY}/Pٓpe'Va\ܝ`*H_2;@LMS(ń?U7g4o>ϑ Vœe{ Uw:~fD+&JC4r4f.n|AcOc$ut[CTqcMN[J#Ln =_xyH߇SKtz#&sxmn7g :Yxcp6-8|nF<_%lNr};W~4U,ZzjÊ͒6CDŽj9Y-^xZ1B. sZSxS4yqfK[zN–Mgk%^X^=oɾ ؋Uz8xv[!{ #Vn޸oEžK(ϒ\3Wmz~wUjRVSC>`d(Y7nv휡<6BMYl1ݢ"||Y+pL"fYܽk1L#ԆS;/r(ۿ]'Pj[9'qXuFr׬IAD}. '""(H b.ʠ6O8^'Qһj:*/K6[;ZYkX*%X n\\ܺo<wMegN:D_~aYDz4ZW[gv.^mHe =;E m(R;*ۯCMJBQYLOS|KD#u~fgE/X̚=ޛCk4 Y8Mk 4( rlZg{ȶuqk]hm6V7)RU3FX\ E9j]{hV9MrTM, lJctxk](ͦ,:ruRh#wKC F?̟*xX)"-+yf(ByYlZW"9k6w|v7!=Q|Vܳ[<|(Ş\;1}i]aai$^+3@_ZWkn;6F' d2#LU\Zl#Oy-uDF\ lD":q;MQFz/: H$_hL-MPpFBE a$\PpFBE a$' 0A!M=@@r/ͪWDcn}}/,5 ; xrr |orO3NŽ/kLeʖ#!;#.{G}>롺Q+߸">,cU>$)fBl_K dZ1wb;R|欙^r`ڛ~b"~4sRLHcN`:Tq %J 6l޸bBPMKߛhgn{U}% sE|i] ~\8#.U#װ`ŧZ6'R\bGWlc5Sa/-EPUI_,=$k7y.VBClWǾ>$˅+ps!@ucYF]E|-TH<.@8 1ᑨ>(;G  dO;.T:OcT{/(ϒ t*xǛ.(b!;RJ hk< #hΎVG5g)}GܣB}\JߑD"A9;T"\ [J;5T*$ cqϝJTve3پ_: qPCh6]yK9G[#f{l. yc)_Ԗwj_+ N{(O8*2~ΰZW=hHyXpN(vNn%ZX]) Ju4Uס:G&¹$h27W)ƶGNH:\LlQ+Oi 0<6Uְ1~m}RۿFW= U& , }QXRfd^7jDq.7KKt|6a|yFF s`ªG;gNvpw8}͙jw9mLŮR~O\\Inw[Qlko'[?3`M?Bi`z|譵%}q}ݗgr!T*M˓6Es>QyiӪn>Z_TYM.q<lXzrE-`~m(diJɭeovvnOT'cx՘XVcBoT8Əϭg{W~)=~UV]Ӛ\ *oϯE6a3pQkw^0精kZ4\\#З(nR^߽GƼ@2Y37t6Fӷ,YPMW3ٓo%kMGX^>X~qtMԴ 9b4BaBǢ=KoLRPC;*}Ωode7_#0ZzjÊ͒6CDŽNc單VäRTK=||6X{!y({8L'=`؏|by un{[bB%|cy ?}PIU\Fu jRsi-:*‡56k1U0͗I}Gh㛉; 9I L(#"dUT_`Dlۓl+qqq ϲ=ElדluS#rD'm"0 .H5K.*~\P(%Q=0oX[[%GlɃnSwlt6|<`pMHE պ8Z,L~3ܢ:&휱C ~p׶ (N ܩBsÜ%b_ ߕMl`ʭ(Nb>уxqڏ~;V%xnnG4:G*^:2ҳm3gm: ti$PpFBEjp92*&9DbžBrώxܞn) ݺ<T*EuAuBE a$\PpF?qtY/IENDB`cecilia5-5.4.1/doc-en/source/images/Post-processing.png000066400000000000000000000717271372272363700227500ustar00rootroot00000000000000PNG  IHDRssBITO IDATxy\T]fePDA%wJR s雚^gYZjfnYZT7}EDQS1QEvd`.? Ȫޙ|9s{y9XX8ty`ai/BCCa\\ܳW!88'ji}?К^hշߠU>8݇L}~'j՚th0$ 4i6 ωkpⴏ Y{#!+s_^qX{-SA"9t:f87}W߼yy=% M$a(N:{ -Н{Q7nݺzz0P~ _1. !pٰ<C?urpi]MQDnKOZ3uF\n jJgl3ƭ[bEϣP]{/3xb11 S ZYJGZԿv̙Yr_U|]{A,ۻ˔,_FM\>Џ$h0>aJzulY\F?- = 뾛ni&Y qI]20呹BKWr΅GSzqi0husXq1=_M"hH vhn9~~?DwWByxwmhPh@Г: vDAs8s~K&& *{1dЄ oQ/P)ޫW^zMZ'|NQOlN8-H"(p0p: y["*?!^w͑'y%r%&J ߳]>BZá5 BuR;oc\ׁS_wJ:TyЮ.HLzc#tBN2NK*W^G8bW~K6<5ܔn_+ @.~R=@Ñ~_2=#rw_8cŖ׉=[_}9]B=z| PL_~"@LZbTG}G\)Y\F@y%eKv_\!!5)g[ZdȑLZc+LQ@zSeGӷn}) -ApppiL8Mz-KҪ(y.y`i ,,x츭a;-Ӱleic0 #Gŧg.q/e)NB9fngŞ;pb/^;q \$wiMO}>V8Ü(̬A)yؼsl)0W2xg¤u;xTZy:nsSx*\rk~ΜXtug"}?Ao.T{cOlt?}x!/8ib@QS54OzvqצvSĩ۽z͒'H8{zԗ(@eQ{ڂ JHgr=_Qd@Q6` W:( @qV1% #zLT :w48c^rA#U?We'Gh/ut9PyiEd|~}PP=zV^y+ׯ_~Gr<懶0h.#YtA޹#qEKd2^]FϾdJ]@KD` ԕ\}|ߜB޸}_#*lK5 mfxbuLٗv iwI0ި;fbfFȚm~XW~0A[B)#_@\T̬67}'ubeVZO RnoG ) 9YGq\y%n]N.yʑK;ֻ,fb P"cG`ewܩ:MͶ>4<>__L1MeVJ((0^V4vګnfܔ߹ݤ,Ɠh]/=ʑ郀Nj)(PwZ \ ͣpZUIyvO095CCϐK%:65Yt}ֈ<BIz1fL˳IQ֫FǜwHܰ9oe<]]\RMHzT!:-FjT߃1dwWP>W}0OVy]*tȹ g͕EW>6e>n_dg~2z;޽ Oޫ-]OE_{xG kRN+  fmͿ3i#o4[fi3& @_Cz节賷 L`sI̸xSLs!Za[]?e@Ҏo\SQ} ֙Kg|\VaM:.Ku\6 ,mlcaiͤM:.Ku\A8Ӵ$i2U̾ei](u+Wdgg XG8NzxzyySĹ[a6{H7_^W߄R=ܥiYdv2 CieTXwfCn? j!6-xENTwェ|Bю)acUü~n^~k&{cCe?"h]hj2Q6IA/\W'"۳'qO½zxr`0QBG@ [[(Wg̦Ux| 4]1:La'ʤ&zv3 ͏]oEEDQ)Z0K2;!"\܍~п=Zd3Th6g "_G1iYb{>ln )+O<3Qtﴗ͹W.V2!E-QZSqNQA=jA4Gb\2Q \1G6)t~S"\;)E~FDLL 76+qQncF =ґUu \x?uzВe1c''K"uJ5678I-PQEgvxݳبwFU:."ߩ~sɬ&#s6@l:V[s֩.H]9u q/ d={(C,|b%3z U_ۥ_>h{kNI3>fA]Q) =`&3 &UWTR2ѽ]bԼÖ|s>a9Cj)PEoybJ.WX|OpkK.ܺ庩ӄCI]t`yٟ_wX03~*♎ݟ/& qff5ӟM;i{߇%1\>TQau<%gai<-Tɍ_hcI6,m扸yyyrY ڼlԬǥih4zxx4mxje2 AU % 44ߛ_eOU+!"%%$I$ @Y +`6h/kծ?OL[[ ARBQ+CTFSVVNQbsVMrj6c4x`;;;K!IY(J߿cǎ*Z sLAjVXJ,//O2=z9;;SElf.A0(J*Ι3GV?|0''0("_˱,|&X֫DB[ ^< O-XJ~KIl;%|Ri0&OܫW/\tFIY9Z 9;;WgBLQD"wssT4MGEEpXP /"|ߜ3y_jǑ0~ƧulSWĥ(J,ƍC qrr3g+ss(p>A0 U92gnExzTFF*GqD)kW,,=fAP\T'LffCY XҪ6C ˄],`ԣ-r/~ɆU+oP|cKvS @N^8] ȴ4___LFQԥ˱8 A95 c/MI! \$95Lu،` APqw-M3o)r@Є$..\^d\DO&}'Fb[3>mڈ^^^ݺu+((E$N7f Bͷ(5vvvO#luε{;fJn5׮}E#::``R:]Jb _;+w/g=a[=&ذa8=vR(3(aE0,X疛2bBQEQ4++ 08O#X= "|e>7SGZl$[@Νi6L<[ml&>(&s.P&c(MBPTַMP7@_U@shI&cQKl^[ێnh2Rlj̵$IE9g2(\(M&SQQ8nsm,^<`z^B>~ݺuLXbOF" UE%''_vf6˱XJuE$##_( w7IhPudH}l6@PPP~,i9VK}~W Hpp0ӽKKK!QVR\\@ :zyZJ5 MI13<O(8^߼5a\Ԏ*-)'lO1k 0 Ve2"(T!܌8&iX,dt1_\,AQC/6".ѣۛL&GCh{.߾R-Ѩܾuh42$͘\>,\Fu%!HdEíK:MFĵ;wH$WWW. BBCyBG.߮4M鴅%Jl,כ]7L )) ATj@f*j`8p\wg2ZSB(DlD\lArBa4ixBNb\#9|AHH ـ\F>LjJ@.;`km6PZ% ya*҉;tr@)j^^^999L 3sELA HAxx˪[Vͭ{ܱ_El_{^JF>N1@ߜ)D_Tɘcܕh@,|#rSϡ}qBujٴ7juܶݿsqud@!zX9Ak4c7t4{p(EzyH9]HR 畳3t;cC;'.tEf cvNcq!3(m=р?#gwGӽ6nAj >﵎ȿwL)Sֱm3* ש3f&UwDId:,~TZzh46ztYݴ=8KѸw'g; *rPзPz8.*7ip紛c5KDA;u;ƕW]g}zHy)mPM@gEX'ъ|.n}tٽ_7J4/.yu;qK)g'vGPmȻp0Sn5Rm%jG7wzsspT6ІbNV z I(qb7+9s }|IJF۫nt* 2@?y5K#\>«.]ϮX C]R$  \݄EhC#J꨽^BywGhVmOӏ`|7@$T "<~z,`L?W{sWp~uB>`np$U˫[V^ع>?M#}7|ֱW3 0qF@\0"f(SAڹףsx&"4Wˌ7czߒ{-K T= Pp1yxYtmDߧ9.M荘 \!Om9}ry>6덥sdIa/nNXv[.52&1֮ He?*9~UjݫPuLm&'r~Zp轳O\;mTJ,=xr")WQO}jWP={a@InB+ܸyˏ~L鳻GB8`2QYWNڔZpa=2 \{> ˅PV۫%uܼfbvu+{oZqz{bjD?m bz0{8 |srY+ƌۗ?xR H8sf^MNRuT\XlPDrg fA>iM$gy)Ou~?~^kGeyEVꁹ^ V;\M:r,32/eV{{ݻag +]D?V)vɁs{)ecNt|#zUtٝ^߸.ގb*:GMLm*3Säz?v2uU(...̲T*`V㙅86`ݔ 4hD/7"@rE M%-9Y^lPAG/rP.vDsxΗFh]MǪ:u Ѫ~Sg@\ݳ'(2dβ'^r>P]JعEQtN*))''cǞ8q-(z)@P[O|y͙ `'gO7 "lDCOӔP..>B}\D5j|tuW׵d AxoO-О&ԯ9J@&kM_^؋'~ցlfZ>(`0Bfd28nW7kuZq+A7XwY7/Wg!y`yvLjóаI6,m扸yyyrY NdIS04]PP`4=<<`j2j@BCC=,ڨZAP$I$A0 d%fEzm5~=)׺=MNNN(J,_q8 2h42www+c6(XlRVc'YBI2EQf;vdVJc"R3Pju\fyyyח#((ٙ( f3sq$EQRtΜ9jÇ9991VGQAX`3a>v}Pؗ,CuǯV.9jg.Ɍ_T ɓ'K.39x~RVvV%HpY;S%H233j5MQQQ!!!\.>cTäxY'-۴"y&_ˬt}D?Kwo|pc%GֱM](Xܻw7n 2 Ϝ=͡(8W"EH¤.,T)Ȝ{Q񼽽 /] se,WzV@ ATE^bbcdao[w*u8Qe-Uݑ1dNok?(}HPEWU: tvFT}5K[AQT߾}W\)su.\pPEQ E1E1+ Tڵ &rbi؉7mۜOnmՁ8n6`aÚYGlq'S5]$X3.Ze,00pĈ8 cOgfqCq\eG9nBP(+&[Yg]eE@ @58nC9֫Dc|9 ^ 9"R*fMWܪcaU0'pPq# EQݻwql6=v*###t8Eck A R?*דML&}hAdOg={5|ɴe}S-&f:aՇI)XX Z Qz>w2=p[ww&6^@FFFd2.]LNʩA?h'N{iJ i$p\dJMMfM:7ky`3熿aӟ#wsHny?l0Ǐ;xRřð"X[>_2>+z-YYYq8DZV `-Z E-*V}6P~๽EG̟28ؕK(/~\SGZl$[@Νi6L<z6Py(1&BPT*& XGojKhi֬ް[ß_s< mkښؾ9c>MSrĵfYnpp F.OL$dzd7MPg)`ͦ}km!Zsnl?5fvv 6c[0NfAP.OJ4Ikf FpS'X;lf ,--m@ 7$@i՜nYYY(RIspFC3NcB BV@m|렺D"f\D*LƼ///+>jZZF$3Qc.&q™Tj}4H,f(xo +l8.C8ˀ&iQi4WL&$-%1b<A< wsA&,-Qr8>>a:JP AG//SU#))xF @k\p&,~+Q%> -=eAjL&q\$E"DzBW„{4-E" U.+yxøۘ3]#yֈ=z}&I,ѴPڞ˷y, Ehr4,@o]5(4، e}s6i7a雪!HdEíǔ:-k1MwܑH$\Ab}4\]}ii KKX7%ܻn2ARR RԒZXU qW EFziEj8p\wg2Z~秄VUZq' h)Q :qq! #a6fs)}0E)a\8l\@ (;3iETw(6[`8:-D}\@Q4::+''afpH0Ҁ Bb0yz L~aggaXPPP +,1`dIT{@DA孲hD܊J AT*(a43C\,sa2l=@@X,*aR#MW\@uW3fgRiy]8<]ժܭ*Ĕ:-%4g jɤvJր(Ѩj]+XE,oix*h/Skrf+E6IYYYZV*5lfoooggT* TNO+ TQauK5r̂csMn}||mf8fJaY5{+u"Ug \̳ C waW( Z6cF Y:|֣lHM7+Ś47R9`0,[[hS[uBi4_f2d?#gw;VY-D݇͜5ƒGƆ8oR~ "7gJ)]ٹLqzlpރ6jn\#P0uzgPeB HQ`N]sў n]4w&L7tF=Qp-y4YGPRUS)?(Zߧ4k::wpr.]S*G>tޢA}[n7TM D~o.TɤѼc:]ڸ묏_tr?;ej{Nɟ9VR y/7!S4~`J5B}{7ܖXm@JԱkS:fu>uȧ/?~G៓_x)YwW}]տ]gkn VoiAP)~5sG7w7$v,l.bK׬YN^}9>pbYS\eлo_%&+ʪ}N6&wpjbVn2 Xq7I;TӀVsoV Vyw"P"3'ψJR!2.gʽwέ:ss^Qڇ"1:w*MPh4B d^R4Ksu6ü|jGz5&W>̕UvO__> \ M#?<"9S>7wE w^פݶZQz Ab&4e<>d%n@i0"y5<.M<oRyPG_Λ1oI=x&"4W~Ihn}oc~Ur.N^e܂K7(6\,jR pJޞG!wP*'!t9Ȥrj5QCD| y8 ܎gqG?(gGUѐ*W:^060\ǪQIQ'D~T|w2 IDATs浱.}t]g`JN:׭*"Zqr{Oy lmiz-R/BYdÓbb(Q;yy{73Τ38 4bjvI{t'u^/h(S _t#zh.CljU@&k`Ic:xR H8sb`qDdqss- B`@'2^i\v^-WV'6bΉ:_ּDPۿTS®hhV ,UW"9˳Mf &<{<&[hR"93Sl Ku\6 ,mqY$8?o,, ,m,mt6ⲴIXei&aM:.s`0 Of6$ME$猓EQzYEQt֭W\bxȐ!-븭Ai4!i߆ YA=L ģaGb]^4=zh|QD׹sFm… 3qҬ[dudmg;I_\ؗ['e s}c2婗pų5[!u&J0p W_Y ?ǁ5%[%wf@L\WazdώHf,[7*s5ȱd` ocYU7w9&@Fdw|ZE;}l E42fy„ qܹ9rdܹpĉ^ qMIŇx4DkiǸ֍OtlP@`B)ߘx6!R_bu`fByv7,jXgspBdm?{=NkiI';;J5)HKvNZo\oG1X ̣&6Ig)ae= ;Xֺ* fK*VTT0B\\}UnJPzj4"͋kGCK^`"UA I\}viƟ҂œ_r V8BE.vDs~*ha*)*ȷ<' 턧LEWc V#v}|r)eZQ#FX4(""Y&"&&FE7Tz#z9%?7^rV\7f{ü// Oؑ߬@/ʌe%Vui}|ϴpd#Bn>2NRnk s6@l:V[s֩V{:8s}=EW!suH>=Pέ/>uTII 899;ĉLoESN ښ|7rs&XIuMw+S{4%KwPx>}' 5]կuH0/˜~Xأ=O+okzL d{ǻv OSL+TߏlfZ>(`0Bfd28nW7kuZq+A7Xw[ރ̫ܺ@cev}PeڶW`ݚ%t^l;%|Ri0&OܫW/\tFIY9Z 9;;WgBLQD"wssT4MGEEpXP  d>(oz&9mꊸE޽{߸qcȐ!NNNPTT|yenEǹ!(B&uaJ}>GÍo㢪el,2 H(ndY-jVfi.ik/ŲB-i,\SrIS1Awaa?0sϙ>s>MDpppEEӧ!V\Eq ihzxs?4w*chH.a/[8qr{ZTtхßN1Mr2op_r*o.u'qȑ#WX!J9;yKTB(}%%b" (@Z5rCq>}2(N`{Ro.tbuf9c΁ ~QJOёoQyiC(}&9oм5wWd~am]@{\VŮ`qafZz ?yR7-_"!pUzj,.P wL!g'8pĽslü=_%p@Wճ0afcؔ5rCa#t)k/,1! po ` V!AiHGz-hcF_J(FRVޚ4ּl!D<5TYwGeYZ} %%5e;w~p m8 M߇^z=ExaZ`l豈6Ro-i:D\۞KIN@$9wCBɲ,A V~ NBp)a7tC YY%ݻxN^¾ UiZ00 f4i%x'"%X6VXV_u{]MAZغD"qFcrr,)}PۉD@}ǿ9ɱ2{i*uKҜ9;lKQe2xǑ$i4x ۳- hkY\Ґ\|[j/VRix l\ AAeBѨRϚyxx {%@[6؁F#mKh1r†A8tʔf:f+64\E._*Wi-6]Q04&hZh^jZ\{m& i E:|H7(@W .7l\f!r0U' v T՝3+ӧO?tGp;TuG#z8D鑈+#ǀxg~gj/'S@|EDE9bǽ7@C`hO^p]{kDXCBzT"+g0Qn^a ^z0Тh5Oqsz XJ@sЇõ;Vwm q{xhuֿWdcg 1Z-T#iM8<M3V2!ܕ>!f#=R[*Y+u nÙ5  \xsq#2/f܉;g,chYPXjղGoA^7$ڣ6ɮ]u/EY QHJ$J?msƵc0qKbm Cc:RAl37EJ BaQLʮ(Kd".iRs-%]P᪲*ATٓtngֺ̉gC}*1S3NXxץڑ>mmXÃgyg9 ˫OUqdx Fh5OY;L7MCf ?.vVonnssO{q!NvXǝu=.RXߊi]]Xm(Mf @64N]ru_4﷣i%:xxJ|} $Mot\; &91{4?xһNr̻OEu '1V%v.3hƤoߒI {F~Z= ۳FTVXs& =ckpfVwСaDDDzzqB*J>R/eSƨ. q72~U|i]IiwTD-}cX "#ZNmؒP% gWmP[ ~wL"4y#Yn;#.]aU%Fl]<Ug?7}("̷._)G7!iA[ouk<Ѿ+_ʕm>lF7KҜ<֫* (i ~ܙ6`;K&*[9ݧTVT*Ƕ+bjsKO>nHsܩ'|vN3$ M#MToeB΁(zy1Uu 9uw覔Z$H ?ָk09BUU/喲(cDA+ȼ1.dߍ7~w̘1Ǐwv9.ί*j }dBŀG޽GZ]/˯m+/ ^~̿xoNkzD-XL7%Oû;;}FXlo=S~N?a;}3n=?{P@*7rSAÇ'O|A{ q\LmʯԹH7r@Aӧ]ycK>]于vI kIDATSq@a WmjҎ|KI{\˕O$(z-QRs-,׾X_>_ }d#>O߿n[U7-?/l^46}:$-Z$Tطo˺zU"R](SGκTt"F\퉸|(N-|yy9I%FZӧ! P-_[{aFXeYa6r|^?mD\<{{{Cz/qpg4zw+DҚ:LNqwwemjkkwկ_?aiDN!.ZN -:`ڐ7dȐHiZ92 #X8J`좢"E#!urx'ׯ`28nwrYF5fmm5ziR^Z3f6lF:s$I^Qxh42,@1ݽZ8-<<ϯ'ON0A"b9QLՅeZ5h 3r޿t(lLV+mZ)Ç_xqرNK8E1)&a"eʊ ǯO1AWTT>}:00B(m˵^b8SWbITtхß_QԲע0 Fz'cN1Mr2J[zu VZ˺8nȑ+VJǝTl}ykt2V>hS~qͪU^C;}f Ul1n8 ìV_2/\"pv!DJ^ E9^>~rjZIuw\:zR}[[UuKp9cK $H5 ULf˯r73Zq# q0)(ȕ}Qp, 7O !U]"aYVV߸qf6A`A8 zKpҠ T@`45'OVOc8:.AAA,///))V$_MBpel& WzB@hvww?rHYYYHH-\6]e1\DW?˥pKx\r_mv䶑GyðPB (zYux4AƿV Hň Haa!8c<ɏG!_g,9ޖ$BCCy(P QY@Tl6ygFMtks? D$kc6Kj_?;@_"sj0d2[餧0 N΄K) c޸Q l Ƶ  Yl=t/צsw3w$LjKӴ`0Q) 6z,J?]/%mWX9tC}\ gΜ  ***BbK@9Bh%<0 A(FFFlDg'O֜7ؗE/ 6Uj2mh7*E8*6&L!2 xyy!0 i5 S\lk1 CQ'XU*h#B }iN} p7gϙ/goWY}Jt΋Ҝ9;lKQe2xǑ$i4x ۳- hkY\Ґ\|[j/Eyyy-vxy6˲FQR55%4pwwAAAKl& QxGZ~bv  q\?u:l7GWl& hË], (h FXg4N-?DhjZC6Kl>Y~Ng coL bO}\l6.3ffk`9?Uwlt]ӧ:w##rցCr?HD鑈c@<q 3Y3T5՗fE) >""Ãf^!v'].vam5"!D=ETϏDيG7/E_=y{hQ4vzᚉO'9 =,y% ~9jGy`rԊ6Ն=?<:y_ F+Avߖހz* ́&`'NO9Sʅdžee7YTGxU˓7ZO.+жB#s mɻ|~8˔ۺXʇKt*X{S&M5&2Dr;޽{DrJ$ÈUwZAL鑈+#WG":Hm""C"= @,?n'!C'IENDB`cecilia5-5.4.1/doc-en/source/images/Presets.png000066400000000000000000000043501372272363700212620ustar00rootroot00000000000000PNG  IHDR:6نsBITOtEXtSoftwaregnome-screenshot>{IDATx{L[&6 k@#K'Q"+#[b6)P$X'QiV6⏢ LZS;APD $u$bGx0;qǘp>;s{0 $1 p+`ŀj`ZypN|R;m#44#]G%51iL w7vM-}]~vYpSXU FUe|lÎM^-TXő3U6ێb^=_Kxrc{]ꐈ3斿w[E#hθd_)z.kf@9P!l@cnvd%v=fki}$"2R^`odDܓcaz@O,5J 4![- 3~{z?yրj7 g+ $lrn!{o楳C{lʿ_x6ā,83_nOn9}[1CPFbQCpgӥuY,B;'o|\6 v7+|U ߺlvڍo ^2peǕoS {] lje#?E&JOo-n pcն׏O}lOd"l_aPgqdNw mgmyg @k_iUޒo NX/z=TT 0P-<@@`C `Zyj`^|̊X, ٹ{pЫ6%%%77wbkā  6ssZ_zWy,S 1ܕ'Rz&g z&-1AXsލe9iſ;$?$>$l ~p;լƏJBD"xddd˖-!T*CjhT6'<$ Z>{,5VJ%ZPRx<^UUh.\x! σ@UˊK+<)T6T=M[z{V{wߒq<9>l~3/%JG!B!uB$Bd2iZBQTTTQQ!y<`X#ɢU9oFf]WsGDc9s436~yoVK_ˋ+FpCvŋ|>xxxAKRzʕPGp`f nEDdoѡ҅<. !{_< P-'q_<rCQ^s1A]|0'U'xFS^^.JBZd20B~ztX)GN-p6?abSj6Y}~!\Ap,fJ%ㅅVVVҴo?~l0DB-CrS.l.v;؟!CDŽ=9?=/}AdDtzk8vXC29ex |d6/>)SZZJOLLD(J$ʉL& I`B鿩?9A}Zlp\>S8CiAg?׆>Rö!'!cwډSXi*a_y@j8za6-'EEE<h4J$Ba4-*F 9WI_}s-^5^tWKUWOרo ?.&4PI׆ʧ_P 4bh4V 555Ty@kI0VPHRNgMM Ƙ$IpT*_rPh4dtr)zU;;7>?3έtਫ(ZV]OWuJ'&'DA$'D$I: e͡ϵZ3wey=e$yvo O㵇0::o `Zyj`FxY:.IENDB`cecilia5-5.4.1/doc-en/source/images/Sliders.png000066400000000000000000000722431372272363700212500ustar00rootroot00000000000000PNG  IHDRhsBITOtEXtSoftwaregnome-screenshot> IDATxuXڈ(% vw*v`ر^[ ,lV@D:T@QaawǢ<,3s; @ $m@ Np8\6 @T?P@ QiP@ QiP@ QiRl+++}}}G’UmX[[K:LKPW/BHcB *0zxݺuE(3OI O| .\ y/D=x!TVT3A$''XYYI*%GGG%X)WʼzzM4:TrcnY7ml `z}1mgiH}/, Ys}X-Fۅ\h8tt3ž4x5lsX/6܂l47) 0C~Ȯ5/-~|QVmO%Xjąp t,z+WΑ+={w?vhu7??t騞M>^"dï]L+.rQ.B骪ҚX4O>^5h9:*so2µD ף B(t )N)ItXLnaIb\쩨m/#7RJ>sEUs};}V8b(Z48*PYLSf_/<v37t )L9mr#"p؈ԟ)?^:^^9l33(0kPZ+*(ݰvecME W{Z^$kM4s(y_cjzqo?Fu4iDiFk 0I#tq$}dUeC+hV;EǕkD<f~nk%_)0:BYN JuB%$$}ƶ ojޕKE!LlSφ>)CBM2#hnm3j =<UQURF o/:}M:ñŜn̛~IDDF^ܛsnjZ:"I4xNh8FiΩ~~ś'5>s9Վf|~wd n=w&ŽVEJ>42v P|baB C UKn4M̲"dxΎz4qQWx"o9;;=言yp&vNY"Byy]mHy9(@e^@ Q0aF@=# P~!WuWbwUi Nobc3xѢ@$JUS1_/YΡk'B$$><>".KUX#h޶YOΏ=V!6SO}& $EE ^^|d'^/*}v՘MޢPIK%d'a{Z˄F~7;(wW7Fϖut1sՆ^ كm4voaaKafKìϷ;CeE8n5\U6]Zo&)Y4x=¯:'iT~Iq/0N;sR[1; ǿMuzo@5X-g<>HlR89]oԶ;]=bX؅]#yQ9Q7,W 725<fTc5iЫ@d6FKGsc4n&fuyx#] ] &} L*(N ~dZ~f x3 4k[rfY1LXt@Y_XC|#C'/}0|l3oѻkേ;c!.~ڗЯ!!?-P(ƷBiΜ-[|ijc#R}, v _9ʰ-x `+K]*~ŚLIMu渉1j.~aC}u3xs7f`w-q]{]Y]|0ų;|>[r<4e/kb+pWhoW۝Lf3V kY&0o_Wm@t?;Aмv iJəzF:8r؆! 9t̖BsozkwFgD̛B gy߼_9Sc[rdKb-2-9< ӇaMqb[])f~WsĊQ ,:].'J|^d,_<jhk78D dX[}}`ikR-h>s<罴Y4qp3rtLͰz/rO[Yr3XHf:Ɔ9ٹnجqsp_*a,_2ÚKx&`&qn{+&/Jz 7{|&/xqrZ1Vډ~o 4砄 ~դ*X(_'J*nRVHMM0Va~.IbZZBl4O" ՈM#Ϛu;+OкtǭG\XthגosX=avz.@3iؼew8\dhPt]{wE _&9.qZtb~G;f=J%!OaZ#0Z:_%ë%kmblQɷ(IQ hi$S+B𻶰;Sf(]-rP,si)d3s[ydžl[VbwDu!1MzX,#s=>J)塚a9pΓ}B>ӟ}7WR>fdI$I'RRr0 +Y+╃X8k {`g,X3+2~&nR_#E{bUcYXPaYPPX֗_o2}ѵٽ'o8ᑏG>9nUzn֬Rx 2-:I1UDOU0F-+X[@ގ߄RQ=aL,o2{x`{w]y?SҥƵppr I[JjKNu?BB `AzDn0]eQRpj@Q~_wΝ# tu1 4 r ~BM71ڵ[.(iAI/NO6>?ai #(buqIy%x_y|׾iӋ_o2ɼO/o]x3pOiqXpk>w!I>7%=Kpbo۴Adf5Q+[l2W:F7f_&~WYtLMsI~9015{ͱ3ekB!eq;,,Y#S5FE5اGkY2F|~ AbR{#;]uݴ-Iܰ ^ґ=4+DᰆSxIٶi!oٕM}~ɇW7Kn8\{TT*JO.gpz#Rc[rI?C_l\ $|{ۛ m4SH݆-ڕ5~-[J'6i\Vs iu0/福ۃs甧K%ё-:o }GR}$H+&u v`뽅;ԥ}d7njR]beT#[]REUzS"{<+k])&z+` u!/͌r7Yl΄H?9@>0,#tngѸ#F ߃71:g2$w;dtOͭY(#o Yvy9ZOw{"9 LD~yqOĚ39x^yD#&Ə_Coq/x*|2cj9.{Ӄ-: n{ar~Y@Oݰ ~%9"h"ʎEi;&8nn5G!i.ZͼlS1gێw=;?ZYf.4-p}9ՅYRx͜PrB7_vfdN"R1 iw;V]G+;8.]G|Xף`v^u~ZK\O=szmSA]M ʋеu ۶T;tvru%u+5jߤIkʘí#O]o4NwBTEԘכ $mv,VMbT4DC ůV4jI iӍ)DEWݐmiϲ(ו *Ѵu8q3=mMnڈRjF|$J(MDD{[choi9$@ NkftȜTm_AJJJ݃0000 #10\e #WuW*r{*R=F7kv…CC(U))) 6Ev]F}F$*(#lQ-]nI|"3=32&ddV+IRf-[4][ U;>?3==2&RUSUHMRMGZ@ xvr@  @ * @ *iNC2ӳ#MR3i \I:]I0UzՐH⩱I2TIbf?:7ix#}xKQX[[={  'qq1I'q~H'q}fQ[B$f>?~[DXw9?0>w' $1IR'I ~!EOx:&)P&oЯѴU,?}jCQ]&<}wTT #slz_!(N\ T\o^.&3IMhv㣧OUm4(@dd䙳WFHP8IvvҽIFQ?.f;Wvp¯Z8)S*9!ǀ;*!);Um%&N"gJR-ˀ`U+Ȩ9`A%RUAIa mҚUmU&L+gJM]Ro)TMBItpx ;WA4#v:$?{͟ hd b.RrTœ<^f``^l,/?cffhkKq9+=i*YLZ^ڕQZiXԕC~nAU-Jzp({ZUA4#" ^я;WY^Qt_3s vIm'o|),UjhEir^|)M=w~ANNU-ЛdlŁ;[e1kTl=k?oPHv?FA<7$P]_T^AAvxxK" 7O#iedoe뒘ic:ciTu?/n:6ݩW?:wdĺJ½لfZG${o_ 3'K]?i%([jhoe+;CefW7:F |7ɦK3yxXGf'CQ IDAT^o!L퐣wDj]RA^a}#LžZ'yy`v޺5scsxj6I   +/)HDfN!.Cf;ELCaUED~_  2fTFҫ&{zeVS[Uc=}֘X4e베Dz# VxjUMnnoOݜ1>&~n)bC'{}O(CZ2s ֠ôV^%_6 T՗,ZZ [muDx/ >>o6钵]Or+Fnƞ9>z|ʞ fM]8pqcX8V|.M柕1ƭiKh[ϛ9wY]:pPQïn$e |):GY`U MvĚ5='&C^B<2釖&@gXU'IWUF!Q,>&JJ*4T#UKK[GXafYu ל0u.Iyq Jw=jl6iԫ8{F8WV쇷?3/ק4!/* Vq3e@=6hJD^9Qj?GǼ~U%t=+6N5y;/!/<<2*Ek&37qnfΦ*lrDI!:Ջ,(#I,SN1i]sKvo{ ί ]R#돇z^$i{2Sٖv`Ko (=#hI2LHjexKyΒE7tNp},ap&nD2T~)_aU p@ Y,Yq'BU#pS[qsdj, ,- M¢²CCɝN]եs<K 5MzXI"!Kӽ3>H]tϏOeRA acޛI|3VnsRu"O+;lqLZxQŒu1qdYQ"V⍝bviK[9EՋjwEIG, ȂB-- Va^:_)qD!Rt$`E!kG^=ow<.u?zkO̞ƴܶ~P?*~ΰ%|}yKxfцO运_ٯBlʴ]|j[||V".CQ'v:.W̠Z:FŪ5cq'똚b"o`8c* KL^PKL8 (Em2|'}öQúa^C%CwI02Lxj9x_s<6kO̞{>B/Ei#&rx/$GƲ~x1 roߋ.Ww.7@5pQ̧a%2c80:կv_>yER(-8I#j^P yʮKyg6l^9`'EEݴ^ BRLlX)jv'ak"vg[e$qjMGG ds8Rr9tngѸ#bnt*MNǙF8K*!/eXty6rNGe`x0XWRI 4l/;b-;5˪a6ow=pMLكva4ak]2dzTJ'f̑%뿌;ę5y*yrNr!e| P%QoRI XgY_3VpaX-IQ5McHP^^ͩ -$$[9v9OJҡJ\n!'禧Kw ;ODIQ#$8LsjKțAś%6menmXˠʌ|"3#;2:XdN @$JͶ277UK U;>?+##":©I;~`ECddp_K6B!P1tR+K Rcf[S>\a=%ey59;szhEP%MҡMC(#oUclr)gB*Iv:jC9sPTl/uJBŕ c?+5coϏׅP踶NDi Q$83Ⱥn@ARo$zPtЪgQ?uwRTf+'N@7GBg/o,F='NӆZ?פߺ~x|=vƽmAf*u9dحalggzRh>;ݩÀm"/g&OnѢ1wlk`*Svy0j&?gl5F V.1-ͼu˭=,5F/<8xӉ=Ն^ХJx w)x8 >V.O&89O \|;?b믗t̖%HmBBV9`ƽGXiM)GrRנU  ˮaDx| յ 8jjR,Z)K_U1ٹ3āU0ݹ%Y$CahW7׿{rE3zHN(uҦKŷ3!w0{L~ކL>8V(1hCM~vtDVf>!!+pK]ݝ}(#2nS('OF,]uܘ |#0Fs gN.ID45MzN yujD-.AY_/7-f+W.r 5zvY=e?vfB{uzlHBOCV+W.m>ןʁ֠IFU_$v߼UfdHi[w6Gzӕ B7w`S*JtzmR-U[o3F2S<hݾߎ\)uJ(Zγ$nz7_O(u[V"\(M$Y6 *E.c45뙚lyq_Ki_#_&|NZ;?鍅 -sIL߬C/o Vm׸E:Bha=Գy{6 Ǚѡ7DXAxBe?5lο+jYgW񄸬r6J ɀ.O W:7`BX|LT-jܢnb$VT*bǛFFL; &mZv첮#?'`n&"'/@X7ٷ'g:s6F: >wdlfO(=;c]H ]팙4|u<Pچ_"ާe.4铮U(OsZfaQ>IbŚZJʡrh4qp?ȭ aoݺe6]槅mPNo3"HOGP"|×m>cRd:z8M|VrgfԱh&R$4Y+<gJYӡXA,S,Eũ3TّGr YZr#* Qnپ!rOuKțsiPC+~谷ƣUQzXFbs>@4AA0Cm&>r9S3ӛ3UmLqي$QBXԪX ӿ%#,,*0fpIjB7o=Ռ')9A!(88M"n#Y[ Xo.fu}-QjjIi^SȊKּ-zѯ'їdэY蘚FG_p\T4{8t)58cYXD),{R2WǶq8lȣv,8KCKv}M-VQ N,y gMnIIOVo~r\_7{bJnj+'J=yNOyĖǼ|wÙ9rDaernvk_{[(TJ%-ޫ6ekMDoVwu_yJu)>!U׆Сn$23gFN]w{ݭz_|vVnC3ㄨJ~EFYϯpX0:ޞb O[t, RNVV)11b$;#Qj'JzĚ_xdh,Fk8kkZ6iliMl=+I+EF،^Ǣ -8V¬",,lkhaMҡM*Ͷheh_U{>yTdDT*Jl6H U;>DI%T/QMRMGZ@ ᠞@ JB(@ JB(@ J#m6Ң1 G#ŐFn*j7T,ȕCݕl (ʨ^2I:TIbfmm_9ǯ W1tҤ̑`U5y]$ AAAb|#@FPzeZGQȕCݕl]S^ϣbѬ"hyS't򱶶>qH #H~ s9Ӻ(_7I7I|#W|9š@zO䙪 c<22diTbɘUa+Q+u$eU-iexۧOiTwQ%22JP؜i%Su y{Ñ elX+sHFCG92x䗆QI5:tU"3&L3**ݛ6R+UAԧʱG?Mʪmu)-lI C<$5־f XNӵ~Ϗ+^FUjt ''@ѝ$p| /]IOx+[Ug⵮uIl[&ajԥoᗼ[U=5KI]߽F[0nu 3p~"\V(-]$i5o(ИȦK|z=KC=xwtK!LloGDՍ' nf'kĘjKߢoڣYdm4pa'!݊=?;5N{ޞe/>=/W')yz\_R%wGrn~Yį@ndekx$/, 0-ԀMVJ9lκxmef#5R;t3=M6vٟzBG=5G?Z3lxމ϶n[KX~86ńoo IDAT1ӸAjl{b.2_ݾv˻G9i`̼ u贆ÌAB)SH_5%#ښM¼E `Мnc'X#~GzOteQ%*~HN1hg _iέ%>Y=7jR>uk .w,8߇?g2G~Om;NV3]f\ŧ5:2hm&O: CR K(63N<0wʄ.7ȍ?3p?#oW9ZFƹn& :4ðbrMANY 7M0oCǸoBM3H>%|KhFmrVzy)Jϯfd_4;`4ߞ <;!G.v~]/\9[2KGOXAv٧?k_rn \=eX:hyȇpE6v| ^?>O3 c5mZa<K|R5m{v';ejn31賷u&.mto2%u q1$WR{RF6tj)U2:pTP1 iѴF7]8Kot?N`cc40?KY=62)[aZzZEBK|cQ^3לe;o+A>?Ynâv:~;uzu0asz S$|ɲeF̙m(>?}μ1#OO>ϓ(Jص3΢PM;Pߢ{_*fDrDI僖PrSPX4ݶtbHn^!KWô zYWg7δ~v q{$ttҭ jj>tp#<=Hk Ѽs?jZJ|yВ7p?Kެ(;x?c%o<,|iBqaI CI. SO$U'>{oOOHyn5-6 ϋ$Ei_8W$u4Ȧ]ކ uY8LpEN4Qڻ}΢䲵>Kf]/ܶϕK !q zvmLc?u^bHTqCAkAbH <}pO4Oň?2yDZR]bfkR %tg 9ɳyC|N{@0qͰ.nrϼ~3I'xPdV׸n^<1yGPj2/HH8idoO%ѵg7Up\9ʝ >V=Cy3h;v)dWޕ`"{Co3ݕqd~3F$w[B} ~۴G_N=+~FϚ_Ql\D#sny?n~c#X4XΧG}ʉp,H?9@&?<> .=Y.,JZ\u(#o2]#XB2vnчM%  ػ {J@tXh6vxMGxy_~c]w84ǀcwYȋr,t2V%N>jy۝q= 7t|j5T\GW]s96թ&e :ffmbtouNNPqQF/u;Q0v=xr)*6FnDITכ $mv̬ fm?2dg`UQMzF+)N7XE]5A;ˢP_WR'RfVEˆa 1vQ@kB07E|$J ɔz:Q7lmmq=51=; Q* FnyмDIUT/B$*pP@ Q8:@  @ Ҡ @ Hƃf[X400 摑Fn*5"ȕCݕ` i8!#3+"< ©I TIbfmr"hV@h4ic1"R֧.\000+[_& Xj\I:]fٷr?O4%(4޼I1ۿG@O^a8V0.8ih%MMݧbT5fmwSU"2pddI/oR!`ĄKhrIP R>< G4Mn#U""JPK>JPNClU7Uto6;q\bDUǓ>_*@ CTUacZn qAW} XUJ5*ddgUYhqxr$S*ӗwT TT!T:~9(bo\t*P7I %Ӂ1h#P|m\D32*?>iva:-nZ4aIS.}.[(|>R[*\*00Ptx ̋ӵt mm).Gr;tscRL\}ۺTua=y}7/? 쪖Pt%i=vWgk"y&:R鉊ww8DTx6Xٰ"*%4)*[H #R$$ [&L~yD)ݩ) 0v~g<̓]hi^\Ӱ7ijEi*'ýrPle7l"Z(i㐇TT(6Ͱ #ћd]knO:hrAN?>\?]M,3e}e.0"?E ]Pp.\(MHUVxlviBBvPƑyMZKvnm%qߵS/' *..{N$>8qf)C`wn_xH5XDc'/w\f?E5'>1 z _ J)(T dm\oF&0Xd6nov!Z=yzݹqE(e(kL||!P[v/:0G> pw֨M |rd ~h/~p-#];w++(!6'(xwCWCa|xOϟQQa2S ^RYRا0&AnGG0aUUEPoSWjkZ.^gR4iqPɆ ~6?vv|*u>Sq*bK/_Zz߻i~( ;QSa˼1Ġ׆^'1ccМPr·H ~+iZkWO{o _=nDRii@u7A#z+)jOyWR2zE ;׿gԤ"կ(mxf:gڻkEog?GG]R S\rri4E1dp]rlw6Fx'nM*WZA'88wAߞk7|$ KcUrPZ?vl;k44:&N=Y˸UCt< Xn? z:Z{kOȖgSm94J(g%c2?:޶AN>_>q\sf]9@\ԢA3qCW6VƧe'{X!g'Jڒ^p}7-A8_MiMdkκ[fFz]MH%w3nSW}>a^sȥ57_Lښغk@z#@ȗa 8o7*UWUP2)2.y>E PWRE 4̖ظ>sv&l?#Ӆ_> ~9fij{/u=_+>Ŷu*.J.4ƠGƥ5xH}RcoRE 4z:p=v.[u4)'B4,[>/m'o&c kF,xFQu|VO;%d\ q (C4ނEͷd%W. 񦰻ϚǨ&S 5og|nʨ{{c߬R!ћDKMZ% ;[K~i R lm lm8(#]"fM++vkI1&P+*.!<%^]$Di|6kQnIY!! B5k˻+**mNkׯGO @@o覲<++q8UcF /,/.OԇBAzw8X(JׯOA  ԽAA&ºId)&&F\(ɴ474hlF`p+*.Jb%ÌEk>)>Е$Wb2ֆz 0ŁxQqi+iI8d) @\@ iL@@@ D~ @  @ Ɉ[ƃdZZh [^1dzMвfwBWyWb2Vz( /PTREI),QЛ$AƛDPL&sN%x K=Pҩ^CR0炃@QE %PJ{C1(1f xȻɴ`w雌O;Mj EIX[[| (" ( AP kDQЛCқDPֽ,F] cYGZMWqgTh5X鮚;:ryЕCޕ{]{&-'Oa,0O {;"aX/!RҕWIZKi]$$25KjTnǗ^Kl%qfMGlC<2 $u&Bƕt3?+OEܯN#7M[(yy%ȱAvn^ HMMD7 41LkM}qZd۾}A8q/̐5})R_3 c85ivK@z趣Ťj@x11J*ej߯tT-;AgzN-IEc]iԐo+rDu@;u<&1IWRzIglQiXЁYBDDpCd (QϿ)(q1]x(^ھ96_ok #{Qo+U2FE@;9^I;~ĠF7n1uzߞײ65^[ Sג1oe+J^zzUtqs_w1s2҄ԎNN:]̉ʣIDATxRdUBQ[7mmRZ6獁w>z KX9xr*Dw[w%c;E-,Zos菽Gf`mǛSb[eo%Vr*XL[1o(yPn:pEa[=mwþ~B.b޴]ru}Ԗ{3N^Ψ&KYt > 澸G ?e΀^%a|xuCf>;yF۞կ} _[r2\DQ%}7 FώNbǺ'M̺2j`vt _ipeH'S~-okӆN)Sst; :Q3pE Z9LM;='RZQ_5<ԗG !9!R*҈FíWIf|J+%}k n;z^gYU>^'<K>LFD`傽RSũTtr,%5׸_ `X܇]At.Cvkmbִ݆w!7?@2qh ieĪ&H AܒǗ@He_}",4y1(L~- H&[[#}DeԺp?(XvɉBnoT3ylԛՀҫqABcdëGT""9tS퉼ol@A( s}0RW&, I\ǜ/,?@TT]֖+=rUzs[#򺑷b>=vS%5 8g 9vzH]B^?|\fͽGާO͘w.L8E1.d'}gv>;8˷'[Ӡt2E_.uQ$ EeωPmqc9qQ%5 \}:|՟ -`L!Qj.`c{<8!>O6Adkmh㿙xUaxyȯL G!)R 7.f 믠4Z3cc+RRxl6USSTƆ̄A,= bzK^ B:O+=|"dywӉ[=DpP Wܬ)KM4+8NAQBi.any( z䀅ʚ` rg$򮔘?yHhIvS$)ۖEHwJI3 I<$ qqy d24y-JJUH$&ɴ6Sa-NjKJY PB%I,d) @\@ iL@@@ i20@ i20@ i2`2z PXTJ37!GfL/2b28}!")%ZvP(YEs~(X[[:   @P11+En ŋ ,-O, KOO!sZ@TuU@.IDzb Q즛]@/ç汭t(e[!~Oن@ H+gR)5nn$vB5 ]ߕMLue+pt2.9&soұL=-( Ût: =`tbQ4HEx11J*ej߯tTuC*iMϘ62~dy9 " ]ܲC+u;tzVIWqxu5Q/6a8^fP C'ش~7xa%e*t(ћw}{r}J[l[-^PTï jD{ϸa,5@O>}?0?Ѷl (~@.LݬYꃘ6\y _kb\/y%431@㎽洣hW+| e*ayy) /8xq%E ,?uAO^q>W뻧_&̙Tv|ѝRl;kA:]3F`lCBBZxȆH8 @ BWfkhh [_M&pcP~+F<@P;dS/+~Gp-k;=?G^+??}Q嶸kor3JxYB$:44/\ J/HFùBn%8 @~8U|#O efV޳v_Khv֔&<@\r<{fX΃q_m0ӟMXjJ^N`偻ؼ=r/C&uLz/TjH3$9Jظ<9[f1l@ HO'/}ZǑG>Ʃ1xTcy>(ww=(jh!\MՑnHQ4i#e /rXEaA[ ~3vņ>-!ewy %\ǜy,?@TT]֖B xkXԀD:p|up[G9~$^2_T\h reXqkl6oz 4 !P"QrWkgHK4hu/ hg,HITMM-SS}2BFB IX,n:#,TGu!tm/dm$xpqIENYR~r\7$Ra2cnڡ' R lmI;5HA Wan% a%jm Z[t^8o8+T>mbϛ&$&nNHv񈋋q&inaPk`^T\9!05%1~fL n>ϟa|@ IH>vA 4BA 4BA 4BA @ @BUTOIENDB`cecilia5-5.4.1/doc-en/source/images/SourceSoundControls.png000066400000000000000000000351731372272363700236410ustar00rootroot00000000000000PNG  IHDR"3sBITO IDATxw@g.F UD\ցkՊuV[Q'PpZQ"*ZDD{Cv?P E}\.=%zw"t?F~~~vJmWMdd$BV 2@% T P 2@% T P 2@% T Pug{|홸סSld ϝ!t`#8nkN^텵M}{?A}јR!2J8 cjkƩ Yg[EF0u \ v7oVeF.saV'5:8NMދ>럕5xz] ro:~ȉ[S۴̽? Z] n4dտэ[V}*Q0gߏ1iwXgt>Y7xXo~VBVi,w,n3Y29d։ v[M,ևNu(qlߙ"yWW }uiA>rכ{ ,H:`osU%+/eXPs:L: meil~wXZ5G_x_0r}R7ͷ!3pޝmeHey+,;*G3oyb[ig'i/]Z9nFL5;^gkWj ߰c$%t']?D¼좕,Vl䀄lk!7o40Dweasx$BůB~ӢMƷ$p 1#"K32yY3wc4iq߶6kΗfMաW=8]FL##Dؿt{Ɍck[g-` 1p^LqpM XVȵC߯}M>Tr.H{˗*9zyHyt5F<{V{@֧R8^tU-`Zt$~_G!ɼgDz_8' /C'v-'/ATmuUjI8"ܪGs?\6sk$ Yt[j䘻5R$L:j{g%]=t/޻eX|SR޳򄐖f64٦[g/.kmLc_m5_m,6/(<'q/\C: Zsitl2!,. i!nmaG3WƬ7/")") 6]T\o+Et2:mmV#V᧢-[8>B5+-NI-%jg4mkc 7O4+fc"st`_!>gJj^nESֺJ=qtݐazڜ26 VY}ܱ[Xw"%1BLA?L\ E_Ӝ2HeZmtX]RY,5i_1;3-YO+_`6)ak5 L`AZkaZ<9.9AM;=l';eAM3?\u+q L6'HZo><#kTIr<mAy'@]"(0 !\۩a<3qpͶt2Nw,[VE>^-qw(-KSIljX=Fe ̿B<C ^j0^LЯ~X>ڲbH$6fg0d:dLIןmze:`:ɶsԅ,1.M\vBHu G8p .vw&U J34X!nMmEv+Yc۟=xX(^^BlU)ĮZ UQ-. J{G썺ڡTdO4U} 3 ԒOz/z~KK\u蛊Wdt]|¢9?ظ0okL˕mnLJ~!;:QڣW36UP$`=m./mk52~ܵq/ȕ8VZ w<>95]+?͙#&a+^L$^ ewkwʹwօȮLϪCA<6$|ڻ dsǙgG@9 fki.a˜F]b!^O}…yY**6L|Liݹvʕ+W.ԷC˺wKf8U풱\Z#6vbWm.cZݶV-}Y'UҦb: Ƞ4:޻FgX6[^X"Fa cMݥ!6n!Cm}[6lذ?J_UR\2֠K(^|v=sZky_Ƈk$ C/v-WtEčɃK<+ " )1:[MV׍n6|󧂏m2:q*$xh~';fP}_P̛vqU,鐕{.\9J姠VwVc;h?z^Cڲ}Sg0Өi℗VnzĔn:,Ƨ~ZM\t&YBVy.nTyn$OWfea)!,*ct !e5|ݖ/7UlD^tJG1ɔΦ wNSf4~;qغmbu5ѝ4##QTk Qⲇ9_5٫Uջq:'&l!H/+ڟޑ\!Ys'>b[S;+Uk3Ig^9]mX…ơsaH.$Lu+Ϟ?kRi,GDn]]ۃ{YZ<~>vQVDm:y #,.a9x͟g]x&[I 3RLG|%<6"=_HIU i?yD^u9u?F! ov \:¼ڸ^6?>rzԵ&ppzA*AdJA*AdJA*AdJA*}JdJR@b*ADsssSɀ)=KKn?{Т'q~=[n3`Ƃߣ49.Xʲvܱ nfm^-ua0QʇۊuQx;aٿ{w.CXB!IJ9Gf+Wn:_op+vPsl2<">}Jw](0F mrF)o ?پ}c_V[vӇjUl7cT 9N#׳yqlln51.;ąjCf6Ʊv6rϞ1gvD|Tf9˞HL@t6'f>%a։qEuPGbC?ۇzZ]L~_ef{@0y; jQxPؓ\.[+ )@4N `sرG+sGlOc8O^>iǩGԃ\˔KtRPa adE$dĽaW}e쨝*G"X*40q?IQ2,OnY/E0˪&u]1oh; Щ3' hKm|n|b8:-M񓰙8"fӐ8[jր0L&%Dy&Pʜx,Bb7iMh:ȆU^ n3{/Φeg; WI`:ΪD0) b@э֊g\%N;kka>aʬx8)3 isRKG2ef&"^?OAtAwQCx9IZS" |;o/?8mNwpZ~MP ΓP QP 2@% TRE\\\ҏ8p[yP Lh4x)zTӫ>[S͟zv%ttt!BCCyyfƲ@Fǎv7fW/?˪M7i rLEbI39~6W3!6Gŝ',F9"c[rMv=˯]6G) h>MA,7S$-@=DBL3gG\)L,lgdG(8fTRh;(&&fJUT*J]ВT}1 T P 2@% T P 2@% T P 2@% T P 2@% T P>.]FxPi AZ*~^֨4Wpu}uV싵oWÅME9eT.찿?%m,8 ԰yٹyU @ibS.V`oaǵ]njrYZZvR) ~PMt!T=h,M<]#޿MTZ:uQTTZtiiiiHHHTT"6t;HM3ڱYT혶bHFܠ->mh4c'iwom޶aj{7gai6ejǩڨuv-Sޗ7=_bٌua_z=f~}>(oS3G'zo[ek| i\Rl2W1Ȓx:>? cڴio?K mmmXְa&LpŋY!lٲk׮M8qذa BeYbZ򭄼Y6Ciܺi :mYjuݧWe;{mru Ju`cm/O]ʥhQ'e"<;cK@ٛqmMr:BCTl$I:tHv mh?~wNKK+RǏO<iii^o,rf4bl)&I|$䵴K e σ i}p7K  zNLZ7C+1KZOJCG tzLXyƪo FńC@=iU:i):qNzDH#țAHKsDL¹Z>V>o2F7Sn߉HAyFIyM@!8ڻOJs<}/odv-Jt,L"chĿ\in8~y%Þ;wzYlЎLFxSc/fsԁiIY< 'w׻L!w'2$IO| $oqڞ-ObںѣG666 b ?~\[aaaTe n bX >jߖ{y:/>ޛ,q!0,n?qn;?uCH~y+]}&ھ޽|;W}SmCKîل~Yڽ +6m@ůXx䆇b>)6|vOEKJ²a0#W5rRP #&_g<tZaenVDZ_9Mca/xZD"\l/%s;v?]s2/ٛLC7C3m=1-NnwǃWFHr{%hV:ԔY]QQ'߾1s =[Γu}7)yyH- Z˨c#!Ю];wwwgg眜$--1cƸ>}:&&&77ZZZjhhW^w1Rv Y$EDIz&fjd2Q夔jjP2I}1ig'B<%~>ez'A忎(ͶM;JO3~[b2iRws5zGU nb/r\r )|.zˡAm} '-LSgeY9}+.2IPL"$Kb)""L#TݭB#S?Jq*1I`TIJm쨝~'G"XJ":՗@4uFopN"Cl1[IHHutt6mޕ+Wbbb>|PkBlt+n߾}UVwt\Ey&hVd[a!aamtҊCIUHH˧8׭֥KQGc=g8}8ywӫp&Km^J9gkq$).8, !IJ==f~t V]$l&^xȍngedp^>*KR-5ik@{q^j^v乣:{ KL'+ӾC??{Y_WG6fH@$Ї777ݻw/_ݻwwttW\;Bs֖/Gϯ`uAz;!Gws>e漕$Mлwq"'6+D3t{?@q|RCy"Ӓq^Y{ rL6f1a-L=̳s"5 2cRX{Y_'IDATM۔E̐D w _~:c^R^5DI# 6o/, %NZOoDOL~K`&&*!/\ȮC#=ڗBHSiu_;kka>aʼ)V%yfןNmL{8}.-!WG{ 9&um1#9/_6116mZro˂ d'lMMͼF蓦^ daޫ*j0m'ROw1a 3Gwgbo.l&s맃A)=?,ǍӼd1+Έ{#cn>q,='ʏ@1Tc/jiCKuW`USvߕ@V¸wGqb>|9KRDBKJJΞ=[>Q3#DwQ*… nB 6_~,ÇO>rIT%6mtƍVOlC ٳʕ+>Y*ۼ(įk*O< TvJ҉<"r\B!D/(μ`JȼJq|}q%v\i ©iw'&gN!$CBh4BHGGgƌG}Mc>ƍRݻw\1c޽{42eʔ?&''N/5͵d?6MJp0qsuB*.-넮8[),VLm3ӹ\={1 QQQwY`lvt:}С<ۊ*A ,Mc2rj|kΖn['O@i 777{(7oԜ0yX#7E3MzMnjIzQ2>v1,zYV}UG5fO>CgZpPśuR7r(鐯A*V9Eh؛n3yŘ.;#JB:n#z]y"bԜo__d v+~B`burZv1T>N"Ugԛ)|R[QQ"E!n#d&j3~%$9vZX~!́ -2t1}l{$]>ZNahrk2hzwRdWc:=-t+ɐjwq8t!$r`456!B",O'p6$b!DJ YMc^#e'Oa@IG&Op7 ł'" y bX >jߖ{y:/>ޛ,q!0,n?qn; '7q ,Y?VvC! *A"%01͐Dd"̈́nlSvk p|7܁J! #6xJji`FFIQ!هd夔jjP BRDR~O"&N[udeٺjEH05 sd)+)I=+*+T81cY[Nnl!ZBf!L9a3wE{jvzhT_yuKzY"!R^2SKM}9񜋈_BIuq}?)1IƽKflN!Nǡ?z}-j̴qKԇޗg~lRč"/4h&Lyx??Gh " ZWG-^싵(p<J @% T P 2@% T P 2@r*_#Ji% 2jPU/SbUdrkBAqqqTwA% R"ooo(7bnD<~uı?6-%tN#=-YH4FlquԐAJ(^ȴh7LM]-L;76ٟq:[fqvop;*0=~˲o/фèIR+IC%_ضdofKBXL4zD5hpkg\VJ75iycߍ2F# c1qLLgsj:+3/xa9͘ q[k WcaaC#G1e!nH!̓ǥ[TL7݌.3eň{Snju%zX!Qobê yi@BհB6V}rߞMqՍ|)[M=jvP4u=f{?wkܑU7uNGb'b* L_2R"IXaӠ*USS Ǜ!dP͎9K>11AHxRDBt}WQI⓸BUqXx DtS~Bq~jj m5JҸ 82F\@Fǎv7fW/?˪^pPa(bO>CJ %DDLKFofymp6լqXK:\mD;OdY닌n_5!\l=7YD7=n= KmolJrPǨۖ_ad 1͜u3q$3PV,!)I.ְӢ뾡?4!ef#yz_[o{:PSdP5C/&"Y:sȐԪVu""!DHf3IDRgv@EЄH~bDoIu_7ԨAUk"+D&$W/X|38)h,!Ɣ8!l'. lDu!A~pxJ"V;gPf q*1I`TorKrԳL3?J+ ì7)^7j߮T¾ZyFIy=|)a![00a^#o-Y3A3<[h;bl)&I|$UKBïFC Z]}AY<݀ ox!?yBNrGΓF}+\ow@j*x8si0l W*)s$4Ņt_ڷ^N ZǞLտ@Cՠe*`X|==`ۡT0n>ҧ7`y)~5x裓zЁksŷqFJ ,+O"V!*U.&ﻢsLh "BR [kKY%陘.!2ACRL6,"I4@CXmH !'l>}ĕ4b?FFO!I7<0 !LNQ,Bm+s2ơJ_)Q^iiO!1i(?5/;R HRg )b0}vG$4Vr3H]a4d?s.z,rbh2o~a漕$MPܕԠơJ>ku7@Z~ëZS";DI# 6o/>0RnL=sS2$N@awg77KΓ< fZgIDATx{@LFHWR-\JUnkEl.KrI.Xu)d-EQ Ų+" nݦV]TfS}14=y5s3s3y~>@ m 44}@ Zz0k!ZzI 0‚IRH$2x)\%kΝ`E ׾ t!,Xg]#`njStMv "n07ꩩNjfmc:;owWf1܆@znawW&d?H{QÅ%"36ҟo#GGD6#4ZnaVZZ'@2+ yFbeU3k@T\/k+/@9{Lf!5*K/n^?H:1NTS6yF˜˺2#cq F[UͬU;>}{/#/$>-'N(QYΡWQ6|%%3:O4p#`5wa~Nݹaډ+?֞ڿVm2`xl/5Jvtww_)Mqqqr*HJ ZLqB(BavtAWPmm%NB +q Z, ӼE 0v?,-d9 orgQo$MQGi.>c1/5SXU@cxk]PI}*N fS;?`D9gQU+HG|Z(Ny׷zLVwB(e7z}2Y)4;g*BcK(yy. Ze`g%])y4&!B9wc6kk5 [;ǦE aӟ&kD  @i"( Ou3dVd# q\q^V]C8Wl<q7u{"2+߾}mW| ZoeҦ فv?k>oѤNG ,2fVLZ8k{bڰqzy y<ћd&^+?\ c4vnx)M2bNj\gEޭsMl<{PV@*h^[b4\= ^zރ>shI_crw=}T/?MoEYM闛ӣxJt Z$L=MJb׵*ّyj~"uN\rRW5e1%0y-adp=XyGǝq4:Ep*ۂg+){GKӑMyoWxr09~N#-8-0qj| PMNPSA'|uH~겷?Ii78fj#YW31G#G,HR'дE J/\)_PݲKK@f ğl.0B> `eu[ ك1m*{{gN4{ʘQ^=(~j+k B!2nV1Gk觧})*eڔ5Q+؋cz^[{ڒ7& qqKSMz/9^@;}-]Z){yޞՋLf+9ݖdakÕ2cX _6q֪,Y"SHm":n{ h?.woKC==KFSuvڒbe1Ch{WY`PL~m;~aC-^䫬B!A2u漢j BPZ:2d88>c >1 (4Xe޾xtp|Oυ<@#}[w^wt@Ax͛ccc \Tj ƹ!!19wX{0?>DTi9l Z5wH @Ml3,9lK܎'8)04BR{="`_Ĺ}@t션Y['IDk/hY M65J>J4z[Ͽ> tLo___߸89 }||׿QЎ\"\5{X"sd!hЍoTGf-/t%RvID_W/uZ_ U Q]]A'tFuׯQda'l6Ҡ(jhhfh.9Cݻeee&qfgg第,5+**lmm]\\:o*aYYYeO]QQܧ1kHN Zz@?6!tB!|!@?N!BCR6Jt 9}&$}hoDN!NF_80N>ȗC6"z t~{HDٰ}n=./o}.<Ȳ |23ˌPC|-|J Z| mӘwO !'ٰ>jOt$!|?(qQ$N!0Q(sӑ4׮𘘸[b㖬^0HCQ٣> lj]§hiL-/6fAIeLd\W˜c zL#;`2~-t!ދ ն#2݌ YAV^bs1,So١C2.rx-l GӬFXstwG/WTOک#)=ӡCy1P}mZ~mk!@,mCPCT!@?N!Щ@?B~ C @?\f-DY Q=`BT?'1&*IENDB`cecilia5-5.4.1/doc-en/source/images/Transport.png000066400000000000000000000017751372272363700216410ustar00rootroot00000000000000PNG  IHDR,csBITOIDATxOQ33m#H L\aG‚$ƸqEӅ@Ӆ+nTbҐl7kb"ը< "(h˴.`gzq/s7sf8 fPDQEL"&ap0I\$ᰮBj>FI-[J<~sCI*(*͸zǥtRhoz_|̥~(?aq혒6(a\*/}x1[;Pɔ1 >2Mkm!|=X0w'7-Hx`v|M-ĂAdnO3MӲ, B(f2Fippc"{!Tyetl6IU1@5MER$˲(qe@omo[u|~n^=Fr߆a[@'@uEQA 1buE i=Hb^"rtWժRU|> :j5C8YeYʛ_kIENDB`cecilia5-5.4.1/doc-en/source/images/inputMode2.png000066400000000000000000000006011372272363700216560ustar00rootroot00000000000000PNG  IHDR/sBITO9IDAT81P{"wQLȩ!Hoo"*͖Jog>;=s iS pf/Ka0|>ocr!h&˲`* ! FQ@xz=pNR-2 ömVUrl(ZMh\.W*cd>Ncٌ;T cjr0p91?K70!Jӝt+?IENDB`cecilia5-5.4.1/doc-en/source/images/inputMode4.png000066400000000000000000000016221372272363700216640ustar00rootroot00000000000000PNG  IHDRRsBITOJIDAT8K*QUHŀH P"wb!ܹ6-u"[i'T4ɤDBKЌ;{|=(GBGhh4=<< IшBl6kټd2DQa`PLLLRA$u:]?ejjJr9! @*;^!aX Id~LVT,@J{{{,CI{L&OOOYU~nw:t:>O YVtvg%Irllh@^'B1BX,rmbX,S^qvvFEQTiV(@eD C?Eh4j2f24}wwY]YYd2WWW8 y<).T*5MAL&q|dda~ EQ:NRA8NYnҦp8jr|| B:պhZ{˲JqG D"QBJ+^lc||\Q___JzYsBITOIDATxg\v R[{ LD4(XXP#ш£  DAiJe:󼠸 ,~?={~_ @ 2*ƦDFFvv.eIJJ;N\!?蜍MOa HS1#I$0$1# ?hri/$uredj7J_P QINN>Y+f9$fz{߿?B (~5" ݫA4nmDZ>ٵ{;>9Ct=9ܙ%U#l.zl-SԜ;pW黭M6),Y|@ kL't{4$pt'RC[6B߄:~l8k\\W_}%қö ,DB ]7Q !G: .l[[ 7)N z+h!M G9pU|MqؔՉ æNnn='9 'jgcce}ȱ\| PTW`f \ZiI`x:M!\ܴt߶/3t7JK"$yc=jm@dmR) F*]6fkNo7>ҹZ%O/gd/+7R; ugzFl|\=p|y]P5fm{y%\fgqHc3UF~cK`ʹߘyyPUŠ˜oYZh@){㣂C߄@[3,y,VfeBmR0` rp9稞gLн+[3P}[rQ5n -ulAlK$LAw5+$f?W;{jFc߻#&I?6Nm /ux#A[4npU_!qeesW+R1>|e8sse 搉:$1c]X5IdK&>Liəao,&{*r\غCwI H~YDU9TC& VT)Bї]71Ub̊>2Lq UC&FfhTgZ`ǿ.ޓO?%"mL#베\6cb4[\% qEf,TD! 9wt:T;sֳ4汾y0iY꒠ Y/o>Ky-9+\yÚWJ 5iiOM!rDݰä_-S׈kwso?HdH{jrm. ZA%Ah%u4/\P_!$%%9uغ3:]reukAK$rA4\!18@ 1h`9@ 3HѝA:@ LySq(A)WvH:gccsgogeY@t(t]7}dK*!"H+J_tC>*;HW(#CF:; ;63HlkX_C&D=-6[ppR=5L5n;~Ӎ0:n'+C0;ö́N4I]>k}Kch6O4! 2Nk%q/߆힝GbWy÷`TuK|fiP|&" hQ[MҮ{xde`ISEem3]?M,(T @QX{#vz+8^xhoHZԞ^|#j%QywwG>F"18p vǩPdϳ߽jĊѪCFMhW"ߪ}Sz)K1r.!fv PTuvRں}7ٮ3^{ );v49u "M({Ң2<2rX/*Nf?ؾ?&/;q.p }}#-2aÖӥ#v?.#Y;ƛuйpQ]ùInܡ$ =Sm?P#"^o `IRK_I q\])_Fx:K2Cm79VjU`QoNiJf.lSz-g=VٸWcl3j[3/t*֟vlOŸKi^>ܦd0Ux;$?'#Hlqr"-sX{`OScy gI9gv*M[س #6%{bI^tWaQS ?K6/24r H Ia%W jXdӐAvHc ]1T WTf8)ļU!j, MEriذJ5\*Jl$;ʐiF4}%"[[= ڵ,+Qul' ]J [$.3B3囔".MZ^,O{r]TW0ޖjyBTkA7J]fX ޤM>fUb/%j9Ψsj~lc1W͏zjF}ylK/^ $L9}򫲦5ZDsWԅ~t#{a,F}}7VIċ&I(yxҶloxSo2t x3"M~]sSfEm=bZ%hdjXSJѱTo$U_>W,)mqU9Obݗn8-~ik[1hw +Y##5Kwb/Y{%z+z{̥wb+ngFMa_?u{ȶy`?c~?2 4V7ejwB7S*=im}M{zǞW.ICHpE_ H\9G„K$`4kcs(Q0z-΍*} jbD ygaK4 d`)z5NQ%"A0l/KDˏ B6>(#L('Sǟ7]~FKa2B [|FO]DT^& L9bE E TV+z;ͼ4MY70ek>_ J5 0gEڕYzUJ Sci, f[WlVtN:DY“Lkf}/-gj8v[Ч'FMs6NgwsS©(XhJͮ14aM~ͩ6nt) ;B+EIj'-###;kdc"AW SŶ\JRK9ޭ?KDš[=]}\^;[ mw騊5van7TȍWwuO/ȍbf\-3b]sS}c5(/uxE˿¥!qd,4iE5ﰯ-4i\-.5}&*|ͶEnwjh蠼HN}Ihqk;Ȭ1LcxqfDL|&ҍ Uӄ~2O;oѶ TcS@7u܃&O&ԛv\mAK}p;-u34WFд99B`s+ fUω%:PPR֞oy)PJ˹,AS= 6 >O~%Vh_ tpg_zЊsRSn7T z7Zѓ5hx{k0Bɵc;8j%3>Q-DD=֝О\W9TL*wyrdnO\Kb@ HzAi|3|vn(/n@ (ʚq-q!\!@  Z@ ɸYQIENDB`cecilia5-5.4.1/doc-en/source/images/snapshot.png000066400000000000000000005230431372272363700215010ustar00rootroot00000000000000PNG  IHDRVޖsBIT|dtEXtSoftwaregnome-screenshot> IDATxw|uf遄{ EA:V~'*CTrr**OEOE!NHuwG I$~>|&L>Ϙ?{ %VEDDDDDDDDDJ)I /XDDDDDDDDDDj0ps,4iaC[e%"""""""""Rc9 #{j}J2tAv[-f,tܥjZ燥Kʵݍ9FYD;ieisNVe_EDDD,L4 dh;Omf,F*¢8Zw"""""5L?3yƚҏ3oac3q[rZ/ͨA5ZLaReFvwe?:ʎaO^]s}Vs:Tvc_B WL>l@Y_3֔~ p| ~&Sb60ќɬX~3S!k-;@L#H^YO3VXowIA:OѿqU?vḐ_ePϦHSDMzzg`ӤW,ތUvdrAÆYL½[ r6ѿg WFr tT~MmD^}ID~OD@xRX:/K~o sso{b O1dQ򢡗gZ:Dzl&R]z zfHLήce@?Z׵i6HNHxvDdeه&6qq.st f1ۗAہAşssVFrگ}(u?9|T]wHm({"N~ ͒bOn~nFRczxU^5_@d޶O)0.se&;q3g;tlG 㟿PN+?4+3cVl;ۊ7kn7w1ź7Iܛ] MoVg{#{Թu ~f> l{,o_t,=q4A=3dș4ڶ/⸜ 亐ztLf?d<@SiǰNR;F `y/Md@Ǭ^>ιxgq3q1p"!?/ Wf"u//{,Ks N.^}UBUy F罾{@ڔI1 7Á#_0 v;N3ߌT#ߌUlf ,ЗH`}yl8ge(~?CR$R-_9z4 əMhҮ;6vƝrh„iC Yͽrn Ⱥ_مfNMz4u6%:&V,h?چ3.a{;jD̙S..uHfnӐWG2˖GpKc֊Ǧ /mEŲ) 9ص nIh ƒcXٲQM:q0Ӽ ΔS$\sw.5޶xM:vECT6VuAEc,^zHQCxg c/AԤ """"%H(7îy@v2"\UR?Wet:q˴; d_'YYvǥ>(d͗58F""""|Z4۝d]8ȰAs<]lcfl~r5jx{b;FMsI'!ufGq ۨ#:la▒ Dy׮!ƫt;&Erz^b$$~9ڎ߸M4mGw|%:m>cl{Z O}0RILn"1؋&xV"""RK8]<96,?]ཫ"gemt3mJU<ݬx pUcIf]$z߸"""R件_ceVo1q.7HtzuhȠ퉈n'=q cF|V7l#ǒ=Ûr~Aޤ+>g/SF=Ѩ*zB;Rr:;w>:]鴼Ɠc2H8҈?uЫC=;zȇF&=8Ns g)Ο4.|~rه!}؇32&t$o;A'`X2=]4ƩCIDdYhݱ1qaX\[DDDqɛ۸Lj?)O4Za9,v˙^U ?ʫHa6cmRp$񟥉1lMPp]N۝|y: ?W },&{Q~> -\o[|#v]$K =q3edW/0µD't?ϑeXYʝxYqyU\`[mЦx$Fl;h9vã>`|IDqus%)7`Sy&o0%YYpF2ʼ3 .ǂHfDq~~ ]2Ѩ[}' u/9msߛdD"b,Yv SrF҅ƓaG]&fp?ET`AVa.$aCfޘYE':* ;&w?|%b/v# 72gAJJ,yſq;}B#&bV4hwS Nxẘ;Eg.8*n<E?Oq %ݎW@s5 ʎEDDDmrzUm_I~;wL,X=k+ڴ233\RUNL6ǎsH2%"""""""""W- d2mU4tqˮ1u)]aeot0"F9m& 4QU)`篫ʼu<ݩ&0aYHa2JQd2vR7cGh^NDDDDDDDDDj/5.hӮS#"""""""""R%V9\UUDDDDDDDDDhoroϟd,z""""""""""UO)5L }UU;LԸ<]m#"""""""pJԶQm6 /^B*@&vL|u}U o3ҽBV,XJso$nvQ\~  y)83穹ȿc)?3ec|?}.<ȝ6wԀYMÕ|m3v|-{=Ti;߯Od(?LmnA& ~2uLHԌR}kcEN2*w|WҎ͋wqmtv,?asM^^Yv)Ìl޷r=w'U|1`ӬzQFE7MbF>iG>'zK9ozq~tJ۞e ϐ[{?s2dAv^`D~z:Y-M<ŝױ7o9fY=;ao W@i?k!^;ECӋ]:""""""""R#T~)2spd^ ͌lx5OUKdלg39?nfܵ 'M݇ގYsto%̀5Y&︅<._ #*/ϖ޹ypof|?͘k.M,\ʬٹuEٻ~++wwakٽcsF0gƗ@ڛrD:w}`:p_s۵o\l<%bҎٱ#'<xpwRz]:egncFv811sxrnTU@DDDDDDDK查Z٪93*KSԾe?qw#/Ea2+ʓwmɬd3ցzݹoxYuH+F,\ ܳV]6\rWm\<|M#Yው#/нjMib9TAKޯe<4r ScuF߈nMER$g, W4J8Bl4Q?_Sww{;!7#qJNmɮ3IE4Fux%#?Vl[m0~X7wd[xãB{]g,s ʉqKc&4L8LLEDDDDDDDD%V 'RX)s ABy$=n\'q.L`TFהM|.ϮYC-/䧌k~НzՉ9'9zu"8s mr,0Uthc*7#أ1ΰol܎群T|{ӯb~[ɏx!x,x~LfO~<{Ugln{?""""""""R)Pn],>)O%H':WeQIΌ7dOL1{X8 䃟W'OgXYQßE,Mkū1:sL)[/pUogcV)bq̈|"vEgIat2V4ƇogQ?+Z Ѵ*O-ӒSIkN3k3F7ܧDq!>xs )CGTR+V*\ p_aFpf?]槿#eMCd<67/ۻ1=x/כv݊[).9€;RDIS=Fˡ%8Y<7^Zz}Bk%b#ɷ[5LޗßaUdr]zfƄ`„_H/;s˨&/Yy*i4䖌3fI$/oF? (gܽ7lㅛcD?;{TT*aFq5UׯaW<UNm1 ,""""""""*7_wsqjE\6G/_TTtXtn}wŒ+*""""""OU̐!7pM>xc2D\IJNam]YYYŶ1TG_lu"d3,jc '""""R2FMWN233IMM-u 20 fsRlbl6db8S"0+%&z%:{q:]Ͼ1TFpC{Ww"""""XAό vlְRm}v.;Ŕ)S;JaeQFأ㋅ 0k@C`P䕊%ÀĔ vGҫ}׮uٶ6\ՔY5]uZa֏}Y5]Vuf5RYWEpj2x`nF:tYbaaaUs""""Gi NMoz==aUXr =AAAq;vJ4h7t>>>ADD/Õpp0/2SLI&|W8pLN^~弄kMdbǎ.שS V1n`ٲegYL&4h@Zz: ?f͛I,|`` i;ċMןGbT,r\"][d6\ԔY5]͝l;y~4 \8+>^ϒVݡσ|&=MU7PMJV}G1{s|xbڶmKرchwwwf|{ܸqcO˖-nӿnnVMVe"""""ř͜ss]V#SR铞ro/NUsƎC=Ă 8x ?0~i&|}}>}:;vdժUݻNΟ?3o޼ of&NH`` SN%22۷sIfΜ7 UUnL2Sicзo߼jn3f |6NQYd <ЦMGS||S3f4)UWQ "1fJWkc̵MM\Y&oxu/2@2~)3}:SJ=;''8 ޛvPe230yU5HԼYf1-U8wupy=mӿ23˖Ͻ;{,/ruVbժUocƌL<,[Ha͝v;IZYI\3<ܹsYx1 6&MTj/ 4&"""o/BXXӦM#<<_~cx{{ٳaݺuxxxg9~8?se„ ڧRn]{xxp8jʦM8p`i6U.]xx eڴie;<==1|Ǘ{iƌ }+YTğ60(smҿ_M0jY\e<>Cjf0f2g3wj7+@<9ml߾{GB|0AL /Od'<9˷| ٱyۍc?Wrk{fmxOŠuӇ_|gKL3{fmƌl1nmi|,>Z?>}a#}F(!z IDAT/8%g6{ˁ뫏}{,ؕ|?El6q=~ktngRԨѥQuljI/^Lhh̙ĉqww,ٳ6lX}ǬZ ~#G2rH~a ;YbADDDDV+)jKq:鞙}II JKãɐqh"BBBx?~< s-+`ԩS $Usm޼wyӧWh=ȑ#INN`Сysٓ~Ɩp:a)0sL+, mڴÃ͛7ӠAJYf<<쳬X3fPn]J+}'i߽s~~*$JCpgWW3k٭1Za7>-h/75[]QL[nSazfVͰ75urKoZѯ6Q˹}>s S背|!A24Qk[`0c󽔔tkE{ (_;u_+xx̣yڊ0nēL!O=}-nfnau*i OO>%0妧w>b5SnWqF͛5_aa6W? VdѣGYv-۷og۶mn?rH}=u]l߾֗dРAL81"8K,aРAŶ4i=z ,,w}]v1l0 Ʈ]xw GL41EDDD-l[ "VàtOsf~״nݚC3ϰh">'|–-[x'JxנA8x .\(ͺup:ThKҿ~gu@dd$y,XBT%FJJJ^Rj4h[n%))@)]ҵk }}yfۗl޼yp d/w[wY-߻w7jrqFM\մYEt6+O>EB'խ+opK9odd8۹`Ɠih= ϐQmy:SYoEЊs\H6ÛEE:7_"* u먔֖v<~wu^ֺ樌oY.XUl pYf,ї&KlaÆbo;v,{/~-oV޲~ .\d{w3gߖfwDDDDꐻNxf INa@6mLJӧcqu}< O>$oF7L%>933r }Ϗnݺ1}tLWs8NZlMIeƪ;6hڴiC||<͛7g}oIRRޗv֭؁$%%r:Ddd$#G+wdԨQ1޽{-?q$^ٓqmwl63iۺ%?s5]$Z?V-br=*:5'yҲuyH &>)&Wwx"c?ϡ݃yցS8n{:5[2rw,&L4UX(3[֞Ωק(=0wyHn77zץπ3eF/;&;8 7]Wo L^"B]_qf#nWW*Kxbڵk>55 V {%DEEq1BBBdƌ9ݽSLo),unu̙;2WNZ$x: 2Y[׬YCf͘8q"?#2a>#/o$seȑҸqc-[+‹/w\>;w{=zkHff&ǎYfdff￳}v~bbb8>C)))аaC|||*4vNhժ˻rk>#x]3ϰu2i/<ޗw"33V+ϵ`8'*Ә ܾ.FÎ= 僚ٍ?ɾpjDrd2ѥe]sؿDl4ܙCq.?8R ᶁmiUJf|;o{*SW]#k?Ƨ^ cƧa B{ӣmCpsfš r3Lx7CХ(v;cQz/ܕ9K:>Ӆ]sp ߄{ng"ر;v6ml˟ 2YCJFY {h8XYZ`M4֏+ dnظo0aX}noφ?qNѿ 2i N˅ ĺ :LokdVe[{1ǭj}~7~wwk?/̙3U+&MbܹXV"""ر#gΜ)FA.b\Yt)_5zkes}ח-q%dJBIMFb%\'=5WRRO=no)syxyy;>}PN>\sw}7/vYYY޴ȲAɍ),!IfϞʹiӘ2eJWgϞ~`$%%y2?9sg3f ƍbСC̙ɓ*ƍcժU=z={GHHf*U^zEMNN //nˋ>3fM&iƾ% pI"/۶:TIբfjWT +e}o#fI328O:%Vc y $ hҰ> -H4m*{ l58[ӆ42J0Ц 4k|9R8sp߭9},˝:ъ*U=ޮRQvȚc?///R0}g=Snf-[d?uncԩѣ̉ .pw#0k,l6\pŋԩSi߾= ӧO'""LcDGGs}`_r 'O,W򶪝={gϒEBB/dng<#W,y1sL|Al<Ν;+ԯlዜ2qN6/.ȫ[$Zj{ Լ5Ɋ8N6wjUp8,Y$бcGΝ˴i8w\={6ׯV;*j…&]+&&.\Pdx}RǛoWqs,F͟jvҦUs "Ξ {SqKU 3#R#3))Bq|ׄX;<2"oQ0W~XRhС'6m*mNOQfzᭃ Մ~Q642%m Jۦ2v`p'O݄I)8[hХwB K;V-m$]Laז mيАzےD@H0,Nd p!,GVw8R 9.\Ȓ%_^T ՚ޟHyrscQ M)}طo/UʸF^hqqqteR4c,Y%K\|:W'OҵkW,e7owDFFAjJJ*nVUVXjF^dd !Æqfǎb`e 7XvJ=+3vPƥȥٟiRnR4ӆnۅ nɒ>nukWZ:VBG[ÿLn8 aqt|<5m.C8tPu!""""""WJ&Qis8?//+2[5ؾm+@IdٌKK&Գ;(mgWS -Jlb:q~!'.n6瓈ONWYL&ѶYIn][n&""""""""Vlb&,Vemb1Z|};0 nNCJRlb&,Vem6r:v-?][ݡ\zADDDDDDDD ޽{5Vkb-͒jUƘEDDDDDDDDv)6vOYjD]_v){gA%֪1Hb^zl6W{-Ͳ֪1cv]a\dRm{ D""""""WݻXĪHY3.[AFFel6w<WCd"""""RXcUDDDj0|4_ॏla z}&""""""""r3LLx~V˭V7&^DDD __L~}pGqޞځg>=~%ʭZ-UK 8f h@P٪񉈈gN 0(wץ q9 IDAT QXԗ&)?065UDD䏦5my[m|]_^[sEd%/˯Pjvscyv(.35 H)׍ bU$X]H͝zc^g; ݴ2d5kjn&M- bW*):Lvbӽ1oϘV1sbrz3rw qƟ`ʯfKʞkGe#o$vew~>T\+oc#hƸiƸoQX-UrC2v7[8G\5N5##-h'/ ezlACb^\y+ gW5aqF wb'N02O8Lm=Qyk>򬍸iD+NO;NGAl\Ą nP̀1jO!8'L&7a~[0xӼw3ZS"ﳚgaiهK-ތ_XƳ/ BЗ~ .#x6\|gK-F*L?q>ƧS3ؽ0{%vQL$/=T_ͪԪ鍯>^T^jޕZ 7p\6nx oFZz&'R9x8z$GNr,$'H>ƶ~A_7E~ԯre,m$?q?qs%EU_R"YD%;,~;;`C&<;tEDdT ~o=4*QFjWSB[n:2ǕggZҦe|iv`?+gAGPw>:ll*VGNe/n3/lr7}CJe.XRbS1g+A"܋6oKnk{S [ģ"""Rgssc )n2Mz^]֮^f+/_Ζ-[h׮={*@%w*y oSϾݻ8⏓r*lʼn kѬI]%UG TWuv?yYQWm&Ĭ{ 3*R.Ge<28 ^pMTp)=%S~g{훥 ;5Ϛ_d.ppl-MIAgSħ7Ϫ ܾmQo-\is7ԯ5f6ݻ7u֥qp|~zөC8Bޣ=_\yXAh@^DDDlGaDW- tnq??Xzx뼤sLxt%Nvlv's*UWd_/zm[t2P g fTLRk09&WDD2rHL^}nF}A`mƀ~X='έ8狳ʩBjNp,e [­žɫ؟EAd-XV=VHOd>6nMzʴnQV-$6.N&-y<+>O<ǎx;}U&] :4_UHIjzYVtn 8v/xxv$]Û3|-CxsB=%lwv{^gd5US>=[ǎlekDGdu̜~Uܱ]:6ZUOcH `Fѣ$&&V@:.,^{& ZnΝ;#W!((;w2aonpS,/1.~1.~%qMfY)SϜ~=_:4!] U+ʔ1Nt!6la=8{gjӽksB5eK3q?q?#ǸEZvm0v,gUDDDsY)WG|Ҕ.S >L 3>l߱ucشu1{0ٽ% 4:\c eSHJJ2:\ HsHKWDv.2rpwsc&tL~',,3[6u<݇[jM1{(~_zhINJ,V)z XV0:\ H`;eD<|`Zн%!qv^r-:ӽk0ݻs4$VGJUt9}nhM *FbtZZZvrJ)j)DՍ!""",+;΢eH: ݺZfĭ=>:leɊpva-,Z6зw[5ktT) gJ+Y)Nfp̳|}pct J9Y-t iB&LdѲݰql#СmcfqEDDXÍ!E(++KRnѢEFG"v"4skvV:=[dR,#1,[ɒ?_̡O@.X/""RکYH1J8X!͎dC A+ɗa<:3u`՚_㉧bf]!\߭犈b).""""R 0kznnw`5BB兓^׷-ٸe7o|2,!''j&""R觷HJJNeƜuY͎bg ߑjU='1MtЄnj8I?-gM ؙ5b1UDDD IU"̹X{$-<@Um[7MDlk?Ip#ÇtsHS)TXsYt+sHvv.];5cؐTlp:)L&hӪ[6`L '_ۇuep1EDD2TXښQ҆pž@;9 |'8ld:FYAb%^J"yVDDDJ<5;6s5Nu1kU38& }Ƭ^0wOep]c%* &-O~?r/zz}Ӄ7DUG0г_[!VDDDJ]+?@5cxw18Vf]өCS/gl۱;нk0g%wcTX)?8o >fEeK8O?9+p'MZ&`j4VDDDJĤJ6U;w?pR&8;[ط=6gּu,]﫣ؼ5!;qc6Xc*Jy8)+y>y=>^NљDDDg΂ _|\]/7Yyxqψن~^AĶLl=UDDЧ@)짶/A8j#U„bMZF;)"o4a@DDLǤtvaJFGrO_Vrh2N6gDOzW2:HªcҶMuyd]һ9!0ÞOfQOdod9<㟯;w?%H?-#Ts`qFpԷ8΁-ʊHIr4/6)~'''Nrc6*JQrEzt0㞻 _*:īoMct<rC3VEaqcӕYu zwq+L.g9k-EoEDDJ|\¥[TVCzFGW5o=nbH#rcf4nx"""e RG0 r6ͧ^x"""e """"R=—t2{M䪙L&nՆʕ,[9lȻftD2EU)76s op_LJGG:Ս&RdTvgsb6~w0ODDPaUDDDDʅC|GX ߉:c&RL&٣%-7 9n-psu6:Hªi61mmzX0:HVՓE˶˯Xvcب4jgt<R ЦMsEÍ ""RLI 9@qv)?L&ۻ--?kL2PEDD>QH9< -"L=+h`t,Wam#3#*:'HF)uTX2%//YnـGDg}XwZy|bܸ&BODDTv|X[30>Ek ViWDDDDф,]}w⹧nQQU"MоM#2r9L~19yFG)5i#orwsr~`ܗHwS2+|7yٹz#ct,BWF?1+#,_={c6:HwEUSm .ѢF֦^Ee5? ^DDDDD.w?.e՚\9DJ׷IZ|I~{Хc3hWPX5Qe;9>rnDY)֌8-=ABiC8aOMdŢɓ=>% }lBюZ :|gg+݇]%RԮU_vC4}=݇Ξ89ȥǪɋVmc%qdc~Vx.w:tԅڮ\mǗFۚ),62z|]W)6m N]\EU `}7UdN$6:HTZkX!+?AtXҺEUϘe)N;)Tnj>&}~as4[UDDDDl6;?M>MVV!y{]Dʄ/߉O&2IDlgt,U3>o1aro7G݄OV1On"RϣSHNq2SRg ;8'ąEXkkF}F劈HiɛOeMfѓ~"E̿o~/m[5$#3?_g"""*\a\mkc!?Ahh(O|4,n׆l\s S >џږs⏹k9S Ƕ'b,"7/UG2kJ.5UDDDDʭ}$v>DyeX"e cúa21g-}4lBn?7aOgcYxmތ# Lxc\+)XOㆵxT\X"ew~=_>j.k?0ɡjt<Ci()l6;~ZΗ#//7PQU n;݋]O1?5"X"""RaUDDDDJ3ټYl+Vo⁻{c#Uqw9)Yٹt&3CmWED*T+___5dzf3I Ύ$$$EDDDʙ#Gy$8MyiX"垳']~ugБ$y..NF ,ҡCqI$G'aן#D*t¦MU\k&r>>jY9`̓C0:\``ԩ]-9x瞾EVED\)>-= U %8"""RNd <̬:uh/ݩBH 28^_*J`~M}Zd(T&mk\k4iu]np`20ͬ\8L&k\ #44ODD|2`]23&u)|y{9숎wȃ>h"""ŮPl5{E:JI`w8?r 999deeMFF5j 44͛_󢪈lY9,['**ޒ|>r3jQ+)+qZwL}"/c;|_L(1&n! Ӊ^?k̘1Olܸou]$''sw`XODih&SٳcJ5%"Wb1}7WӛSW2}'"Z-F)Z,3]FFFѱDDDUX5S-úǿ3DYǼdWܑ5:ǼkH_tsI?Gg{ -Շ}|Yc{b'7.c ӔSk߮.~Hİ;s-#רemDV$4iS}̝ɶZ:^'?$գ lz!21.d:eIs~s"6=##6q1[\wBR{}{9rdeeqF233߿?+Vcǎddd]\Td?urr⮻m۶|lٲY,`)ekD~=<GQѱDnـ_{`Cd>3*MDDH]ݔA7_ַ&Ԩ05 IDATUMy`o0) v,~&mͯysX3ͯy֬\D?ZjШ|=1o-`陆lҠ Ϡ`ƆisTFV>gI> /ym$xWńW.KjpԹlZY׋-iٔ{*ꅳTEVT^^^5H",,4cp\r;b}8~8SL^zԮ]0dƍǣ>Z`FDDDʱ%#fG3a*1u|x󕻩S:G7&@ѱDDDX%Π>ݨ|$}{_A*l}lHÑG|&'0QQ{nH}o7@>W'1 kҭw~[87ں> _e, - v<ӌUU4 n1gO&svcX kFk×έ,zG㞸qY\<<<ĉggfee]Vv'_XX狫6sߟt֮]Kzprr @ժUٱc֭+ ۛ ~]H9pϿbo1֡1d`'cH1f3L&""R$r`ۈNЭc6:\ 7!il^y;g[ɮmGv_r㷳*74qíaOE^r`JG*Ru3Zn}4t ”L^n6֣Khp?Inn^bfWWWjW^4mڔ+j6?-\Jaf|wBӦMiذ!*Ub/0uB/=o"""Ϸy|#*R:3tLNN|<l7:H]qG9Ӷd'T#/1R҄Z 1#|w MYl=V;LJ{ݻwӧn"qQϮ%%%*UL`` ?xyy`vɁތ=XϟOlll)MsD<3<>AFkb1̹nNaFGO@"Ot!{*%deܓq]\JeάXR/W%%ĝ#h>wWji~']LgrCʍ[雷w]PqjrnqX@~[.xNcqgs=bc'8kg4@̑ڎ]:3D@/bsbyUع EuoO ~aL&iii]aڵZLrr2AAAt`"9gF=z4ӧOgŊx"""Mjj~+SٳcF=FkdCUŃo\k8z]lk>+ˑ?,Knc7qLxC~&~C܊9za)|_ _wSGXy_Es C]ҿe\~Y/wmgُoz8LI/}E٫x999ƒϖ-6m:_ϴ0 [\_ՋթSիlfԨQsebbb'OdҤI*""Rbi`OS .Rђʞ䫹,[IjjO<<'TDD@5{ Q ┿9{uՆ"=nJJ k׮###o_m#Gj%==iӦc?X[jE`` 6=z0k,-[@ѣZblڴcǎX`=|X瓕u+""R3&Odb3++ӧo*""VĶ5~tl *"TV57N|}(WTcZ3VөVŝĔv2je7R s]wѸq3W/p8c̙9sH2ꅾBa6lP}8w6 ђEiD䲪U<+oĸnïDDD T՝;hѰ>^4s1LxUe^3c \]]qssoUP-[nݺdkH>z>6Byύ*HxzV4)yѱDDD TՄ6oHPps5yF111Ss5ZDDDJy 7U;HDJ77^=Cx^/<-4i\h"""PW%$$\qqMDDDDʾi3W3kzL&ݛ^׷2:RVF?>/&,`]3~:Xc\ """"rELZ%[Z- FB}ؿ7VۉBzF2d;o끳sńs}FG9OU)aY)WWg1:aÇtʼnb˳ѧgkc 7:`6>jͅ Q?cH90_.NLZJ^^>ojot,XϷ K {6kKDʑ{j0qb&O]ngPcH9g0:@y62mQpcܳ_X"RLwy*l6;Cv2:cVpsH Ofm~*y3ۨ[ѱD޵9/'.`8lt,) @DDDD!//>+c_UcеS3fg5vX""R*""""󷢪g^yvUEDg_cƜu*5ª:4d2WsWM ѱDDQaUDDDDϷ1Yp+d[*""׆""""b|,E]J=UE>'lk5oёDDЌU)U͸oB֥ZE+g8%`s;fǡ!&?Ci/'ʻ>\2N#vR, 1wkf:#NQ]ClĞ6.?϶cٸlA'_ctO?,EGDDJ|9p㕱*"Kv>ϴX bt,)TXR\##D37X? q7xz6$ w&<zFEOV?wqf£qSjBfK"15k'nQj'9qª1ڗ/cNGcHp@w Yq٢sQϿѱDD]70i2VD""RV*+řL6923rBE*\hхZяvoNʶ_PY3T|d*6b#t>|VفcGAѱDD [rψL~148E*#3FEwU\5W3d88pUKn6~v|2Sx"s~&ܡ|ER $Cc$yPRP׀ޟ4Tߛ_ao>qk={\ƸiƸ/^d[:=C߻ h,Ƹi!bɊ|57NЬ__c\4Oc\4e R: =:֡i`Eؘ~\ib`SZa`$#Nwӭ&2y-2U+ tjʻ3왶/RUu|vlQ]p|2v4mܸ5^^ٳGc\4Oc\bZ{Z-yr(-(]٠q?_7ng*LC q5Oc\4Oc\.\\8tWo^pըKZI6@'a>Ts`qFpԷ8΁-^k}BqmK\h\rlgY{nWK#Fx+̬zGNKDDJ1VEDDDʨs ԙ77:fUgSIJN5:R*A{fo^m>ёDDJӏi`NSIM0:B*1$ugDO#(..NuV'y=w1iƸu؄+hQwqiƸUp1BXP_]Y:4k\e֞={hlq̬^}'jZFjLiƸx%3Oqp _+#Ƹiߞ={ =г7ۋV0{'@߁,,,RDv;T둒.iRqo%^vFM[oQ0[G$-9 seBprvfȘ14 1:ڿSOwte$ޞ _rcXl@nV[#ot4ȡfOJB6{e0M.q<~).UH=r-Wdt4* kQao<;0'dU&k@p0a„R$L8,T\'==kR^=.€Z*;v`ݺu=dӓ}q)hڴ)Zqddd0nܸ_t9u I{-}b~V<8~<><54ؠވ~~ F [Օsi&C}ِ%{zM IDAT3_G?7f3#^~fĉ4j)p]}zh㭱R'PV5QaiiL~n~ꩿ-wxTUT!!BB E@wu]vUXˢ]]]+,Z׆bC !'3=!$N{Ny}Vs^Lf=㏯*v~weر8m>˺7_XKhB7=؉ MLeعv-Y Pl-|1HQ:,u/rs=mډuD&%ֲe͘T!%~>x۸q\t)|{ Lhw {eϺu̸*Jt?$-W[b4h}𪩥 jw_4Uת ry͝@i+^Z<>` `f݊w+y4K|sqr3yCFi^{b2-kMߧ3fO%nnnvmvmh4>3>sRRRzoΜ9OtV0L3dRhz+E}&_?jWyxzr*LOgÇW^9T99qsqhVlؠPՆ>E?|Ɨ69Pa6SOxݮ4osnpXWsC? 3Tj5WsZR7oV(B*?5[zٲ3~?C^4o2\1KF$ ?%jy77@dBWfo^{|DRncH0޽DRJrc] XCPxos?˧9wIII466b40 zv} >fW3חF&.eܽ?{v?WTcKqfO;͍} |,}+Vq㣏Q7tT&H~K{h14)ˣT]iGU/:۟T*.[OA--|s\'VU;8^߷qtw¹̞OsK~K5*d* +笏9..&k.F&nڵwkuZ ?OG(Qoŋqru,_3AYEqĐBͲuPyUUpT??,u9SR5a14qXK\euLu`h@EMh\?Sè>k_yD4!!s9"~]ԭ[uVƍGtt4z^i*NWzwX |ʞ|m={2`O/sp'&kwWf u8Xmy|BA x% !#]A=}Ϗ {~mmg,*9\zF{B ߲ zEë=&$,`_ol=ط/rkl7-a111/RS{AFN=wJOW5uk ]4լ{s wI=@I >$FvraondBB''wTѡ$Ы*h|e$:'yZݷ6*88oooPլ[___"""INN&++>V4,\1 =zxJw(sEl*^[?z 41s_rrr0I꫽>goyzm9Obbs㍽>6s6~!˺pF9BG[tXqJfxc:9=Zb /0ˋ*α t=ǹ|<ЇU_<!&6Ruŋ{}l{  ..a/37_}^cjv^Ats9nniɧ?@yEh&g|b~AV~OI_FvZt'}wooqmzf3qğ{nel?+lЖ!X:\׿9*}msuױ履h?y`[>\sRsMl3#l fwIZ,{?73ӍGXɾDOƮonHA&(&i>3K)),7WȄgNӳ̹&_/cTBw[+ S/?,cT̓ˮA]v! HsC9{2 |L؄ )6m }.V8+OXD}Jٸ7koWoeJH CF6ڗVJYfzyQT\uU=LII UUU̙3g}~{W_5dlFigݰlf\}5֮)*;)6JLJȤ$Q574ppӦ~ʘ<>UCI]s͊Wma,\y76̥p3 +f嗣st1$AMf^ݿсѨjJdJ8]}e%eyy$Ιӯ.ML("Īlc C귃63}zU*S/}Ϧ3I]ԯc""p3eJ8]sC$w^~ܴ}5j6[P   羯=Uz뭷Xv-6m믿ڵzGy3L~im6, Gի/>N8y3qI PFY/AZ-;cFp!WɰٙЭ̝;:1cuF#IٸQȄYϛSXnZ .@n F@mqB[ZHٸW H7&̝ˡ-[dxS8S~j*'I瓲il4L{,@.|BCq=4"Qܶ",xH,R:~IG]vYKZ_~=*9Z[KiN_. l(mVOյ_Ǎ"41ʮXmhlkl.n3G56v6>..]ҥK뮻X/pJ@N /d+H"cvLɩZ7n0KFp:vܹdl.qDBwښ)LK;QY?\z筗sP nDƎh>pt6{ժVLJȡl2㏜ӏ6]%]tѐi`uѼ\y9&^zm5UJ'ǎPs Kٰ9s:8I瓻w/MM2D&.eF&ϟ?cg\uh`zMbb>CzQZÕIޤJU:AHU-ImJ΋C_PshV&̝;ǟwxlTfQQF* $L8]޽&&Bqi /5W`yx R!s߬x #FΝL* <<0<(qdo ͤI߶ c{đ72ur-m<җ446+R\J=S.}?$qTBw_ϔ/бNϞaNTWGUa!1r61ӦP[KXd={v8i~If ݳ ͛[hp\MP%7-MM'f7Ď2޽Є~/-*alҷm;,cvϞMCc3Ͻ-m̞9Xfz## v3L&'s41&^p=0uWwOTT۠t5i|n4<2w`<= !sA5ZŲ 疯݂NQ:nfl++QT* 8qf߿6"1n.<"\پ+/WtHݻi=N)Y~0:V_LxQuq1mMM%''SKcE&HI$VA-[]Y=mřHp& tYDU&'3eЙ5m$L8]4y';9gGr^!!QUX(}M%xZ `J"*d&Lpb+#a&'K %X&ښ'bҤAJp|v(2hil` \Y 9ԯ).7KLh4kd"&OF6,6M|V$w>IZ6uzIN2:$ib5w^,$ UAlBBԔ)7s&Yv2Ч1ydϞ-*!ePhOOƌ`i:&OIdaZ6 HUՐ gHyկinQolNrd:8XIp{d1ۿ( 3a-*!%WX__Fd>8P]\,#!%%3|$SS%s2-Jjq4VKw>iXAj*5t3wJ%Sݻ`"L!RB={$k`=mڰH8;ȟom i2?G<$S8&w>IU&N2?I$q3fkc T5ٹm~k7f"Ҵ`S<˝½ X\MBgqN'#!%E/ЙX;+CJdK+ 0Ab1RϮUMa+KN~V.s˘Dk!~c~)Wc[)ɦٚA\GM3ܹp*tYy;do_W+SǠ쎃j8lVIfį6%tƼwp?pW_-c0ˋ (iaXd6a>PqG**'N$?5 s2pYl;.#kL:8CAZKGcǨ--%0:'򿧞!iOБ!Be,:~<~gGtfϖe쨩S8 iOx%͞9ìv'W_O?io,e ))+(HG Iճa&g:REEώ 먥F 0Ms{}{ݻI IDATK}\Gh.Xx!aST~ Lwo o5rS ߯XEŕfyJp 3-G>^kc9'? ),?#ľngE Hu5-MM2~hbXb.}JK˭b6e&?5Uɢbu*y՘Lfb3P%rI8342$>\] 83SG4&Q8%sUUԔ)îgƴX[xnW46dr e?lDu$SSeIEDpZYH 8ݙP4{̄CK^e]CT)CG\Oz ɍ5iJpj ,jƍUvٗŃ%0Zヿ&@V7_7Ӏj ե} '7V(vƖH*l Dn,fѣ?\5IQܴp.e U9'M}V%Rp}кK%S& 77<)ʒeZX__Y4avB\Nx/UZFSRAZ! ,-K83Sj8qPǎHu5ѲOY^Yo@Uv qgl^LF;,4pQ̀͘&'R/}MT:nJVSYmƢv;wsM%'Z*Z0mNQoJʗ1lXϾJqDDJs9?]Ke^ME &h'JŘ(|Uݼ]'Ve DbU"iHI0#Cdh8\V&Jpt}1vtgkO7>gQF<~urHbyn.%yFr+lwd^͕7R铯[e]%$9x1mzGw}ڌ 6(#P/R݅Ud,$Xb>x&dUo>l p#܀CrCdkR n. x7 MH4'GT RaFA5g1xAbKii^A&~íjc4Ztl؜ޔ)Ȑ s xֲIA+̔DqUǗwg)43ny,l䝧N[wj<ԏg?vo('/x ?Կ{~?w7OGF&[&L -MMFLBe ѣ43jXY x>q"_~Y9[S}hrxzl6S΢Gu~k *f V:''|BC) mN΍B7dc$(LOg:Gp|<ř$-X `/>>Σh `4s3d-@Й/bS$WʼnMN^/kA+'{śd$%%t:]w>ӦDsX+/_EsK*8>^,1$[%C(V]\̨cqqsuw:#M[Ú543tZ ZVVAբui?jV=g]?ju !{`2ɿ l]vtXb>@vKJd,C"Tt-XVmz&ʪ>܎h[r43f(0YY/%qqr[( B \T \$;&Ub X<k$xPl,%o@jJKmRYLc] 5:${+kAa`22y*}V3G?.L/AJuI ђ1c5v,UEE5edJ%MB'!NjPiU Vmړ^+VUjTZ/PpACV\\"U*,F,,f#r:nd8rr6o8)̴f*:''<(( MIv6so&s[oddݟ#Sمesjlp>o8_Wb*$sBICCy pU5еVs>8q=\dM7xamCrJNN16UsCW}6L$;/ĝ&Y)ur(mw~nyT*.>=~ihl汿t9{|f1yc99~u˝\:f3O~|w=N'^qCm-︃;9MNFr%7UƨN)LKc s8.ٜg?Iby6ل+αd =b_|eG(d1+}Q˱My2~w879_>;64Oh 㲛rT.gܸ ڣEP¨Η}O_3kֱ;F3]Ԏ8Qr1-98vɡNNZ#G;X::0jJSG6ٱ ,7wH&VRC`LMwhc{_99h3$vM7&+RS{>%99\t6$fc"w5ŸyzT۸qhu:**K̕Pc^Vls92 m:ggf<Fw_}~~,_c4aYR:LGFRGdRJm/A@t4z=&f ř6wMC6S8H ڦ/팛r=O,]@Çhlqm @sk -^kng4 6;쨂6P9:AtT:ZKkN58 x|IuSgtޡ83f;[iu:|)7I>+"qN߫ƥK /tN{g.ch|=od@ѣ QbcΰTmйXqVHCYn.6ڸ;$ÇinhͦsUfʂ##m:o@Tj96z3=O<1H'$ț+.1DEQ.RkKcJ]E6{(+l+)I$VS 9BVZf,f,RrPVK/11fgjF*m~aVuz6Gӣ'm\˯_~i9\Eqqde:)rɝwtNZM0QStwoomGd$XfQZp,__ͧ_Bpt:(CY^TWABg,7W$VJe󕓾aaUVj&{:e#_Ŀ>g1eGWkk/wX^8V1D/H歄^;:Z͖ 8y?s r #s˹: М,7Wj`L eyy6w(+*W)(.X3އKll Hu5ǎtiPVWABgoyZm^ r^$VHdѸrE^'J>%믞ͪyox3_@T[>UfBl\'[)99ټZ@B^OHB:XUL8bLfCx[AբjiXd Vc>GӢm*dgZ-:-XI^=Ղt.cv<2*V[[[A23yzh8RUeӞQCՑjj5cl>w@t4WCսъ!PR]}#/eMey}>4'$6Z& *ҜIJ&, uUC@T"}##ݿUJ-/NiU̦joqvv<.q8Oh(UUȹ f_|ޡ\W$ ĪzMfg1idmCFB+2Gh߰-EϪH+&;ƚ5C"S(YEY^ 矯CIŚZ7,$y._tV+U1oM؉jF+\;Pd(v9%XFϱ7*2A8c{+;"&Du&P|bTk{c.0~+ ixGڦYs,DUY1_q( x0jӖClv-h~{ Fzz:c)YŐ~l%?;nϕ-^ƒ)"bYW9%\^מ!fg(;t0l~\SR; gZMg~cu=0ػw&IӣwyT ''}Y v~c;PThzSX,;ӨQ?Pf Fc9[YSG&f8Rʹ(!Q{K MO3T1@[+"XTxJE#`Z8F.wϻǽ?3曉6Qu7_aZC 57u֢V(Z˪޳|>RIٸ;oO*FÂ[oqCk_,f|,$~,&_x9~۹G@f(0-XH6^?,xoع; u ~V\lf~ow|ϬfV~5{akx y!!Ԕb2_{R+?2 An׾aܼp΀*OxN٧j)x~' IDATLsCb1 pMIUy^b,ZGFRh @?2BC|絷l+Pꫫ Q,>T(PPGֽ+gU`Lx$ HHUZQc*d$`eJ舻5Ŋa* 7~ 'X_OcJb7Džۛa~ONupmCnaص<ū3 >VYX|9891ӓ&"DbUɔIWp0Od)l6SUXxt&E* U y__df0czǬ,(dݨchV:UoaܻX 5x+e'D*BV.nXqެ񴶶U`C:bb(]j8|ɤuPQ҃ .A)I$VATjZRUTt(vnb).zPnGR]aV̄mVՕ=l\e57fOP cGҡ%{Iߩa'ʂyDuq7BC|hhzS MQp=JEWtc7<\TϕrMjXb%+W,!IcTH\4=|]Xj.z5^Ȩ=!;FS}= TvQY C[K ǎU:d/p3:~g xs߬tvsa=\r'4ZN?UJb*Yg%~z`/A gL:\IM/5ە \,究ɤt(v6Uj4~ǃ|x^|b$ܧ)jnNdjwךd]~W(\MBgq߀B+ / >XzRE/H+* |(Za0 靱7lNa˶C8:k5ͪ:R]F]KN]`7 8VDŪrQ7)d\ٷ<{RlsR}BƔa$7(.P{ѡ̸v>1Z Grxp)UA{Yg%>Xή܎*VaTBJAmi\X[wƮP:+O7#vdD+g.> ;Z^DLEExuy|é**R|='>:t wW(w@A]B+\hm]X aښe':Gd.⇵)n2^1.V(Tcm)& 0 ?7k ]qrғ9T7 _kfc+j| Ăn~ .U.'Phh :CAԪU&L WO{PëeLеoysqѭOk`+^{Οω'9Q%6y̮4j" &$%M2>{u5FMǏxh4q大?ƺ:4n aT Al! &6e^ddhWEp jo^rʰ+A#c.+/6K< ,Wyi^N?>ƵQuJ|m+0j ݫyk7sNB/ZKnXkP거yǔ4ׯfwͯdK'[~[/?gM -&YhVY)ڇU%p$b dPeDJjnpKTteHb(8-Dg/wتD !2VG|XQ B@ ZFVww}} ?::imuZ,ǘ25[,# q3{Bc`B[ mL/!۷\QS隣wdR?mۆV^F]&Yv#j+/MgdY?S;/{J`ǦMt&4[fdppm6YH29޵e "ua)/Ϊݻə8Q#^c-$E_;?tmmvlitK.<6kQwa*ʎt_|~EJ)pqpfRrrs`cj<bN My &'S};'c:J`UaXڽ؝BGZZ:*5Yq7ͣo{&Q Vdz`WǼKs!{ fpQWI)6ד1Ctw_,G\c=N"sSP@frr qWn7s=WzsRSٲjUڄ+/0sfT?H[ z(ߢ; 0-F{PqcLvXUkQA`cBK藔T-_I8AA00~0F7:~l>⁳>d܄mgl\M,yZʆ`7E[ =`7 -CتD[`0zXcWU+<\.::HHJZ*:]Kww:xn{MKu'<|sTҨ*s?z.B/ya*c*q 9{ݭMf-Wpzi2MkUD.RN1%n ĩ!>!1g"R$ &Us?$˟Eeoxk\BhVUg-QzxzEuZVc(.^]M0_ϿIaAr!њCګE/BƢlfڂBR -CpN--2YB苊p9twv(g3RvZOo?5O5--eM C\}BKJ(kKVVe+`jBbdSF2¬=Os/)-'Ƕg~x3_?)N~z xӾ5SO)V_1_:Ȥ.gRx OOb0޲?@s‰XH3Vb^Ux\ 4DU-τ!8*Q%}hIJJԁ]N.--*jκze o{OhlHj2 E駞*A<#LĉB{uӴ"~Wx/y"XR]iG׋"+¾c!Wi$E%A(0\V aAS3O9Nn:D6vǙ5S7bx*e ]̜Ⱥ?  )iDm]pp6@QBʚO7ToAOƑK~UrJ0W^)IHJ"U)@FCN'Q&=؇+Ē ʫJB[f79z̑*,9Sh)iE/,Ȳ21_g7Ek"=6-Cpf3y -cPrKKqTW|`uYJ<{<).c*6]_@KCRJJ/q"db -CpfUZJL0l‚*]j "JkJ }Zҵllk-YKJ\2Z/-m_$U'堧uO$d^zhO*&YEO^p55XuXuz|ib[Y@3Y۴v]m<^49'Fߠ.X6Ԑi4URJ IZ-nC6 ^UA^}Uh#v{!P,[[@׏g>u|[?>|#)M&UUOZ?s;,e ]BˈzpP'IK744*5v/;m^-*ph]Kj ܠJIE=Ў=PiSHh=x$;*ge@zWΟ/ N!+/"jHcb' -cPB IJ5O5q͕G]Cwg'-_1 6{Ua2כL8,-S33 x\.R22#b{wZF̠VF; x$>{KmPՂ̶9\}֯Sp.ƽQeA[͢DZm;g#]5$Wdqi|'s3]$Wer)&v~@34EϱPB" l* AWUs(B%1X@Bfn.NW/W5dg/E~l&P͹KeCKJpTW3%՜r%BM|<F# ,FfT*BKCo. /^}w-ym;u\yYxMrTWs҅ -cH cUUG͆Ph)dG=VXJ˞OxmxuIsgWv,)_ܲhGŷ`?9M;sj&r S ުxewߦ+ox-߰t}p8fwCdmêUB{u AYA!fDVc(.a6S4eJT=48si4()yX2o2`2QwB`0(b4jJ9 @B\nQ[Dp%Astu%< \e\%%TV'EruZ2 D~MINM%!9! 90O|x;s*D@6~fdqZ,K:v %%8c7XKx^ٗ;aoniiz;$t0jo-6%e4J`UaX꓈׈+imm-EJ٪F b)efuwtD8y/P%c4߅Bl`@uR ,\Rw799BKƒl1Ʌ"?)?^*ۘ> 8bpNU2Fc ',FJ&nQjT. ֕qZebLn-yuo^Cm-yEMͭo:~|D3R Y")ك^JC P(Pf1@Na!,pͩQq>Ǘ揿<+`0èP:8f+f3ӅU\ZϣINGFCKc#Bˑ5uwQPPNERXbvq,VkDJ"I$K|>?O>/M/}iJ*$`B+0%5Vr8HHN&95Uh)&dkت$OL$-;:DGuu7\{6lpٵu055YTU^]-g*UӛGaNRRHjclT#NeCd'k3+jN[/AVE8AJZ* }qqyAIA[ ,Hɷ=1F+aQ]qKײ*Vn),pBP[NG/H0h%2!vUF *(( IXbUj CQN``bXf`݆{y-h4qwע1FJ!6]tɔsMA/Kac )y0Ɯ#~4Rj+Z϶G8\55aV(nd4bһaH b)\4Q #vHZʈEN)f$$'p9BK*RlA6̋pu04/+@ƺ:r 2" 1@? XY,[, ) 1XpZdO5q͕ gtj?l`%5iLMW{R]EL,%0b ޝ1vcqH 7}N_nN?s:!, '6'& -eDba/*ZƈE@etT*ښ5HbX d 2gD<[.߈ceJPT#,[nEE17֞=TFF ]Cu(ukH VAe^z(?`$ePou9@gvu*?];6a0ko$?O '\T}F*jlU<`nr GVS؉v`"i;) gZY.;MDU$v\u+D?u ְV ӣESloϾ/?qDWe&QC|Å8Yrl@Jl굹44:ud=;{hw$&Iiy݂_/^#uÁZBK15dHJM9vڴԙӈ 2az#I0+ mMM~U xPX$iZI&QVtq.,LC{ɟ0A`U#eHJFR!=e2l/.&. V FS]=ɩ)$G^ Te~xwG6!K1J-Lr縵`0HzNNTNFrFQ #a6a0,Ҹ\}͂{̙X=7tVv?st8AN|ALoY֘iw3iA+_dC2ŗ ]dI9Oz]76޹i ,m>N}t4)G~01xAĘoiSn5Yrg_ewq^nݷ}?*11$o휜H7U^/_H is/挹+}[<'¶Hp{:qˍAh)#O?MjFg\{5J9v;Ou|⯂i9gs~xP%>^zu%~߈+?L~"<>$e=~lm+(()v /*|U!pf二0Sk|ןMHNXy[Zww8*imlSh)Qi6K zU1^/\gAjk dYx twx|<z_|R:.JS^0԰KBX.6J`UAAaDtu%׸ D,uFKHL>ޖDٌQceC<ߢ\{aV~ظ XJjHqZ&D)/bјj’8f v;:s_b3k >} 50uX,닊hcSX0Hub^%J`UAAaD8VEEɄ=Fn,YCII<9-1ǧTRjq{57h Y &MIg4b' ,H@{k+^R"N隣 B,"ɬhEiپUo &Sd;%\IH ='zD*yEe =V?QoWqE}:w ݖM:;7 kϮdXۉyv}iV?g^r}Coܷ5c><3j39pJ8 z&k߼2baʼL(d DW8m޺D\{ N\ax`2nJeD @NUcRZ)$@S}=iْkCVbMds׭㏏5?.3oi(.()Œ_3zv|-BofK :Vll ;0UG4:7:1էL0/e5 !ʊlkZƨD`pՊH8>f~A3n]ZG=IHJ"E%b)7 +MAX0@vw7iY AjJy>[ڰ{mŲo(x Ǫ(&B4yUd{O%֚W5lff(`L&?Iݳ @VOCM y -'b8-/X 11Rj/o>‚hH .كn2XzXf3Gy ̄Y1&EE4P/eX^>Rl#eD_JKr?Tlj(8]?īR22\_!/.fڵBː- .^8:$b|f<߲e %b!Lhc&Tb. 2zn7<? ϞłSEYqZdxD_TĮZFD|BKNy]&1IH -+f|Dfj5):RD,6Hw3)v?B{TBցUY  -c̄VKeX UlJ=a%VBO Qi6KQ'u~TzioktZMvAAMAXZ nƗ-±#XC2Ƚj|UYE_XHS}=aVI\n\Jۚxw d^]-hZ\e2ɺļ,4 BKɩ'& YV2 v<0Vrn MUR4F XUk׳}g%ii{"4i^eB -%bv:IHJ")%Eh)cF/s: 0hZJp͒%&s]&'iK9_2JfC/ *9X6}P F.7ZNR"BCWGiyXܳP7\rpXݹ7׬CVqגKNHؑKfJk0ɂXy@>)+߫NU2 !;_J~sv: |*uuzㅖ2f/XUPP6R)/r~` 4Nkd\+gb7g&rť9T@ucG. K sArZ BK399twv& ,pjDps&!)gM #37_!9 \S^Ap!V*GjN:-9TV&DOwk# a:wZh&>+ݤ178\XL;WtF&3:lz:O' 7ԡJs{˦^f*Pw'Źc:GbVJسㅖVܭTWiǽ*Nfg MVTo$uYtpԐVd-sΚľf+_WGVVdr)BK ;UI-Ћ3q IӌnE# ܣdd1HNfs(Na-T@płPPP8˗ceP^^ۏ H:vwwH{K $eBHRX9nԌ YAm6rpjСQ<8i7 44a4 -%,v::H *Խ\L O MbZRJww7--af Za^3sz% oKKXbVtw%5+Kh)#RQ[CEN^#--1]ŊB k{8rMFހ*nv\+4 7SJO˗r3g* ã,dυ7o+#Z 4iRXseieB'13Ǵppx x4/g[xhx?b|Yв̾MX? -%,y?&UaOb4hѨ!kgJ2K(//gCqLL.6JOZ _q~? ^d5<jkn"~SwEz˿ ǝ|3<3LāO_~aL˵" /5KIHJZNX)//YRbgyvbfE3T+qF"%3 gn95nrjHʒMP PĿV|~p, 럾ƺ:RŽ|;:;dBK rtͲЦhhknZJq͒WMPuWI0O?.lvв"hڻ)#}ZMNA 55BK# }(<{\zl&݊3^yVZ>. '0;A퍕l\o|ۈFl椋.ZF.&'!~?uu 6EE+e95CŸKgLcٳ6 y -#lhHʢfT HhBnє͇jjd]fZǔ/.:$e}v357hЦ -%lL&7mͮ6::˕M75wЌ`6pb,LY{d෣KV^*j3&r1Q:UEnY*2[kCH/NYVg_|.$o>_h9a!yv1zix( 2f_>Gd6וn#!]Zet-RɸV{wyU|fa3uΞBJʚrlZi,N͒!bx9}[Oɂl7_]7;n/<"N(H˶w2Qs'WHAjH3xמjqX,zUQRXp74Hr&'%p]čw7\"!78 ރz|~4h5gZFX1gzewvL3ThipwIy0өsw.aɒ[#oS93<Ҹ>/{z=>~x5'xkϝ͕םI^_Iw\r+KXr?= IDAT)tU?0 1j=no>AUrHF^)R09!ɚJC\*kpSU (ى. ńSr̬ޱٽmnq;2,ɹ69qm*&ӡ_oğUYWXz_l/69RRHNIߺzI ~WkY~c]6nT$M RC}?ՀfrO@2ႛY0%`F:Uٞ^W7ơ+һKcfhޅfqXz'0[Y~D8- 2"Nt[p]H[f+vNӴiB;Lɓ6P ek\xI̚]W;d],m,␙jP6\6JGfn. KJ3$':] ۸NSRHj,k`7;Gs *ھ,8e._Gw2wZ=[ha'dݕa07͂SsWӦH{tʮbS_XHcm-@@6>N(ɀQqƢ3(MO )/_ȔC }qXp fV> ~Sk sے%zl뛑'ϯÖÄ(eާ[[>}du11r>c1K^ "pQS;ІBBt@ Ȳgߦ\U9f[,jBn@S+)eWH6rk"37֦&|]]8.rXCYRDcuIŤp6YZg<``;@ HϘҩjrԐ:N:!!9mz:l&#/OP #Qp|,O3j?>_~{;dޖ ~W!Fc.yv4 Ve¸ٛFj|V+vgBK~Hc2@VA>m大iپUk]iꢵ\%M-ҵLXDcS .Gu#G !䖰XXY3؎u nXq̟v۷?p++1(3gln֌X @2t#'$%r8d1iknFR -%Hau8HXzjwv Yi& bsZFDUBK3@ƺ:rd* a(.o1,*q“8XUO eY}NM/+Ne)S%\'b(Å,]Nnyokxs:&BKN' R&%}{ݺc^w6PULjb*N/y\pĤ9sV+3;NP E+((4~&N>q6rVWB苊h# b*هՆ>`0UNeԒ#.Up)C_T$jH$>qF!AݯQTE ,}džH Np8]sz>fN/ewі6*j`Yl&C[% +jjN3O`׻^NurhHʢ^h)aa6`IJմ3k8}o17io'+!lzy:,"Ey9pq*row;)/Egpagp핧 tuIOYSCNAldip%9#gJ 9ttzlgw59 J,WV( E܌Z!QহòcХ*UV۹hq,qeyLj-ۛ3 P\L}4VNBΫpl9@h)cFTcgp疡/,f#K𿱮 xDĦWha)q{͜)1XxBˈɩ'&@zNr@-z}3ٛ٦Vt2̂+V:GI y\r+>r^XM1U*so^`/)7PZV=N'K5T*աIlpnJGNYؤ:WrJKf\xqsVᤡK@ rAR z6m22/_;QT^^NMu{ʅ2$$%Ymx5BRP<jwPU-&)]~% ŶAwtc*l-Iتy,[k,ԑfL%L>w(օy񧕢3TttPJ0`fߤ ՛&LI\B9eY+SSrs r3g:㸙ljS0q4y^g21gϜ3 -cH34,}f-oihோUQTy,L곺;Z~"?66}Fpp`?2ljjo{.|:9/~/fєv>pѭ -~~}w$8,9?㯜:o.,$%3miS6DfF4ݸ׬G~>=(Ǒ`㏓WV)]& 'MWjy׶gSػq#?xi6_s+;{'>/ Z`e^Gaʼyp9BK ;太լxQ}Cj6.Í&b pޣ SoX8鋊`[ h`q fImMp9EMfn.MM"Q"NL<ܻ`U=Z1~r@R:c}4n@4z"!) N<[~Sz[ϼbuZ,'8h,"'GwHBI ؽG^d]!uIJ4'p i'ğ}3r& PBcCRq)oSr0B4esX zƗ046lK 0lK*]|#R2zz\. )5xlfDs +o ͂r++j=.<~z/;~ ՏmO1Pd:`7 F,,?Ve¸#-X_'[/vwEDNBrR"JI {"ɉlf~@ꆇ̇),@ovA;f3Jٱ*M,^Ģ(;wUe_ݝ57+|#х4̚Ŧ+Wgpec~f3c)AN:C?"ewj ǟM $Px|>s~ otA5e`0@gm|Λ;]{ˆ9PY Ǎ;,2صqK}d!XxYAV)Xwx}B_ÿW|J"4 EZG.b(.f-BBg6ORQZKiI._0@ He{|6-/LM)fʤ"Ɨ員K9T7n]]>6Tp\_0gf3?? O$CŁ:N?Y|u~d]Y23R9zk=(+>jTP9G^_xl*^}3ƕ1yb@*n6~a)>1l.(F_`w?!b`aY8[n|$4Ԉbst |[tuy1o?HMr iRmWo&a@ #Aˬ3gwmiyKmky;#$aezmB)((@Pd@de{h?9^9|G>>G:=컴 %縪lxW`;&F4tu+O{ 5 [o: OJɩHIK 1ErwTn^~:9'x1UUс7Ƿa%qE]C 7@#5Dq E۵N9j*qD-C<_B B\<(As ͭ&4Ѫ1#3B\f. 8ƳX]xܟjELWK !1ҕ`HVTDu(hX;n.^ zā "C7z{'4H5]Gw iVNB m7ES mx?&+fl0tj([vG ΟVH%qprz=rd20hPI&,6<N1n-nں|~@)fX-ڀo?0=|/v|@`s`E` b0) cτ#G{q3!!A -3*Ʉ'>/*g/4cqHply<`q_x ՍL Z.\:Mowu zyy֘LDJ%mm&l4$sۑCuƞg0wϽonlV|~Xm.H>dbժy0ۋ;~`NfVgE]{>? ˥XDR!B.Dw4Z ZMhn1Մ (TK떡@-AFzhGAn\.x b1X_L0.4Q\(:HȇB. KVBIU~!\RQK~ V6Ps0g2[*xC1@o/xB!աP.fSfwO mZb AEWhZK6϶l Spb\Љ<: ?ϼub5!;#L`/ Zwr5E )IHQ '+~"f'eUPƮMi৹PAL0t2|o<5Zv0BrL3LԵLtSdǑ=*YS'fH fؖz\.0h1Mނ9b@gׄ<ϓ1cLu'Z.zL&逮^?&:+P^.𺖞ĩ g둠"v?"Ӏ'avٲ|qͣ =;uD=W#q_*!9m|uq7N='62jcڐV y"{ܛ&.KA P9( [G6toݠ>yked|]8Z+p aWFj: |N :<1/L'4cx *,`^-g83rQoǑC&ܻxŀΑsKRn8&7bMXda;Ŏ{쌐<`b÷Mf!ihh@IIllGIpT;mZSl*ǘ(^~a6/]Jic׆KK!e;&8ZW۶'G;-2G?v>kǎG_;oL& IDATR1T40/ es|/~*_'l]Gj߸eaO?o5$ȫoHǷv(Zc2 W7܀1܊cg{j/ ?Ujtxǎ?n8o/\i18Fs]߫f벞=cmU>"tf1~B]e[}0/@2{^z 4ַ(c"?m0c]wo߾> Oy'yՄkE(jk3ɿ<ݸj믜Aг?Xw1 1zu5gzmkkA l;w=Wao1'~'4 WNX*hj3g7KN@r?u(B)#_+`a0:0*n  Xu:&Vb>̖[z=Vxc#Nr22rrn2!7/p !ߗ8p:N8T߆Ph@i1=ΩUZA鬎侻7``Nj|߿&Ǧb͔Or9[ &n~ /ߧI}>8M&Ro E\ !}o޺|Q!шyʘijiuO ӄ`~H%H -3 B?,Y8Z.wru &#Ns5ۋN?t_Ar?t#RS /DL[&lD3~S lm龳rqdbo3 Cb8`09'Mm|X ߛ-pADQ$ASiҹD 9+0NaXe0[Ū(EI6~S@tRYWZQ@M}7lb ~hKEGg)N$ALzE9XMIKC2 Ftx ǃ̜CWln@]]}x/pX11Y#,.-XDX ylڰI\|\RI6^y}/Q!#PZest #=))፤BCuq< yhwum3+KIp\|>?tm6'+I|6a lv7D#>ޫLn ٪j^A6R)Nx"7H\Y ׇ?e:zIpb^p`Xq*r9s'O 5.VgBTRF\G'Lus˕^+.Ac` xJ/@H&Qv QUcd$L@@-h9er0WLǪUN!n*@^zht\,lNoX dv%5*ͽjR>KQg`n73&,|QZg{nˊUp)6&)C \ UZ V+\+suׇ&#攏p8lb5mYJX*ٌS'Ob<,-",1ttv0ͤQ]UeWb`wO l`@b~N:i4nd8 cjD n~Ũ:E*)Xi/RSՊl/Yid\GGqJ .~p6TQJR'kd4g]uHb XᓙI^MCU{ %8x$g:ͭe^Y??ѸĪHECH1^/%Sb.Z@*[GGd2 m,A^\LAX숦3l\n: T*qrIw<ySf^??~}hr(yK:tM|X mmXC] RӑNOň\~z,ڙ,ƒEEw#yj*k(--%#İXY iwR l }rj '=Y~ %g/4cǤ]}`3N̰/j Y]AlWU6L7>>V DXM0I$LgZZ(6R &V(1BC\:,:4)[P"Kݍg_؍@=hOP0mZ/ryX=Sg!T9J|31OVR &6q.!aۡR >RX5a+n?5V&d` QLt%GiIb6&_ѣ1}$߼ex &O0 Hq`ŅYX;:„]C^q*#i8D71d20d`J^UߘXr@ ɉNP :gW@ fCW7׬kF&NhwctO,6ԲGqOCtcɢ"lt]Fb 2SŴ[y!êARh1oHwS8rj_w7f|D^Ջ^Ȥ|W{\@-fsC$PKřTt3'> cCoGǬ|X NвB@"^x$! Y^D)NwfhtV|lBGlg{'R9{T d#im#!$M(,DTٮ!53QCOo?|8ha k˰cadFEȕ0:$9h}Fp1[dHLǚUsŗG'De8lʊPU%7Ic.^JRk(-ΛT[Tںm4I$V$H0mѵ V*񑛓<"n,d`Te%SWlN*qJ$'qO( OtOjC$*+!u?46mX4$q#>q773vMN(0z;;1K ypwJ|XY豰Ԫ502Nt-.=J i4$|${QE`@n^^\ǥxpY,1#55))I8r'fo,m ӇC_U*꘩ȓKZX?WUy]1Y/pDSU9IS*GU6ұx='H`jZ_u$WD4x٪\2IMmJZ-e {|(b .în/!ZpB3!z"߻O?]1FD^$$`0h-fJ}6KؒuDlDQAUyyBX7Xmn$V /|+{ ^D8Fd悓ER:AĂL߽ [d$(7 -E767 JTh7D&0BjjJRFBaQU$V X.D&ӃndѬc3XM eTZ^\n" fs@P Շ Wo\+WW=\ш@m{md.P(!Tuݾ/cq2B,S~>\:C":TBNH+,-,d@ VKDN۞t:P`4;5XnC$Vcr1{>;M@t1"VlW,+ŖkO}Lm}G-n\7QOv7ĢlH/XH%`00$G71Vn. X~x'LDq uv2UfH&H`Ң͸*/+ *1^h~m:iwڴY;.68i2dv@q ͍q?Hҥ)ŋ٪D4јpƅV 77+f3+[ FelKV= $IɉTȑJ!2å!bݼm >V\MT%y:Յ?=ᰄT;:cҎ2Y\ z,?vWrs2^X0OTuu;8\L/.1TYEE""O9e E64H&H`ZRlWk?g@ N `'%uww}j 23&'颳: 7?f3U6B"ΆR."t,-hi5cQ:DB̖ᖽhD@lڪBTB"Ά2'V|0LtG<,_R>9ADatH6D a,?x`t5w du z3KN`2>RE8tމ4٬ WPI*s a0:Wb^Edש9e GmmkJ$V$H0-ii@ɜZ%Ũ WB Qֶ)>n 9tf"hʺ盱c`2[keU6P0'櫗2^#!s#+:ɈPztA"Άѭe/PY]bJ_UTB(YXmnE ߛJM[WRhhfuwE(!^V~'ƉSSg]JI))L7\9$L+,+l72 @5:TGvR+p8:v[FC˹:Z[[#A bH v} ɘW‰j@ʊQF`_,ͦ:ʘ.Fc8 Z.ŏ:)~!R(ZYѶ#'ƪb͔MBZ \n,ZPHxb깏/@6?"@ۍ@ tZ_re2Vx=9JcE.*h>_ -&lUŢń 7 yωFnNT*X4.ZXf.>8}TC `eb!^$sHʂbA4vI67ᯯA,wJ6X2aNMFu.ctfw_N*zğ&C!Qq#X4Y] ]*hJhNL&%E2biҵ"Q AiIƌze(9鐰M%x`0v[ ΪB'-A|vz|I vy\ P.=oJfo|h#P>{nZ ₹j\s"<쮈_ &'dG{Hȃ6񛒨TPP28E~d2 #bQL ^VZkxׯ;:bG8F#2srIN&m逘@ nZ+VA zvbڏݍ~;l!u-3jsC,Zh7F^`,B)QUreT)/lPTǑ A Oᅟ<7ι_t;~7Y+o~7_9Ȩij1x/쁳9ٙNsF3J .r-l:VtS\zЦA&`|<T!ۋ67kŠx烯Ͼ;v$ 5ZCRD)h8u 9ׄ|몸uJhV]M--{|i!yűᰑE[k6Lt#K(Do/{zq & QD #^.yy YZ] CDJ%33z"|-1g2Z0,)iiHrن_sw؋_,VzwĂH8޸*XU3J[[`4;PXY.%x&"9Gv܈4n Niɐqdd,YT4#MlGYש/`38EK eͩU \asa' $_毯+۶ Q47lŶN#OͭfD)W\)ítŪ@?x?ϗcÄ-p+*Weh1DxnjNr25ry\Jb|zvW>$q}>?^P)X@2j,Ϗs(qD^~IS&CYTŔ@ڰ\wq-p:p:!j o)XMM _tz'90pxzz' HvKʎEj2PZ,ŸuQ vXK NfQ=F:NTVMC#.lncEc/<4q]=`pMpo\]KG7H@,tz ΓNzys ow\8kX:;Vhlb5ip".^qu㒪tlq!3KxRJ1Xl&ZnDf\!RiH}gfKǟ W %%yVCjTh!L.Axw~IJՋ`kk?¶=ekWhXFyR4Z+TJ/5W-S~A/,UJo*3L8G3m`y2U)h1>]>O2ssve#e/6zЦDcYL5x iRZR6a |grw\}bZ"Hl CU1մ8WaӶ"3>tGP lD^U^ld\%T YZ]Mb\DRXiB8tFB|cHsŞ~Xnt7fP8Ey*\  &|~g92: Z-ULm>=L1EtDb5A )>O_|+Vw6*R'{&h(TO"8~nh@d$$T]O5`okCD6X}~<. za|lX;>$nY1{cIJbcْ~0ɺu:qdIVݍg_o} Śsƭ*UtGVe^x78 eC@q6Qָ RӑmR.[/qMFczE96_^zC;zmd@QL&#*9eJ Uc!8cw@;w&(&=%"!X1&9tb]UhQQ69LcMF" AiEs *">tn޸E@o/z;;m ӏ>j#d#l ?gVjwq5²Q2㓬=x}cC*6ګ4w%h~8q<$ mJs&<~I<&;ٌ U'g^kZ &\,nu(,e|*UCnNd6@R- $eb9ɹ@Ty84Z+dp8BRP0uhUJ Aja#a{lAPa0D,14U(/Յ?=!|QV nFFB a|8t'ĶfPV-AэǢbaN h7g4}}]]6j ^*(SUT*ڵ' fw\mWU*XZ4FC*…VdfpȎ |Ҙ5J> 6mX6?ij$%MnJoWɫZ 2H /r?UOBo~B|t9QkwΟEuV#5MHjcMU0BIH eQr=n7|^/r#4Db5AӆVrederQ'9v:$ v4r!z{ttX ~eH8}1}[ZhN7$qX a|\n=3BU K UǒX]0W g{紝[n' U`\5*IZu[HJIL^ddgb#EV9ٙeE+$^oXB_$$ $߷FjK*,]\ 7QHjamMy:TGu:@-N\>n$/ yݳy?kV͉5 !U"V4C`H ؊SgqxMݨb횊Qbuugq<)*z-\4[XrY,Hdܒ4n ]?8 \56Q|aU,:*VmdggɌL9)%Yz=a1 XnNdni0 ia!e׿;o߄BN ziҭ]LE$BoXӌ:-+5:+T2#HO#;noD&6UsM:Hw IFc<&߿!C/.`n2bc)SMݺ 8^bZ4^4d\%sy{x]p/.մh1/PIpTZ&sn: ;=c+B~ɤ&lDQaU %">,67DQcm[+bW[xBag &s |^:߰ ;C_R 7 J N]6**VHIIZ%efDDb5A xZPVOh soC3&Hd@ql՚QE,F:`(@f#34aXˣ&$0Jˊ3 ?[_&Oy>| nYBRM< dWSZEMTb5ȒEj<Q%J) *VHB)jjX1 &dRHb..,Iĉ "N ^}|~htVDv= jaNM Īdu ꨋLʹ+VBEwwQ>=G0ӆuqPTawt@ -T֨=B묚!G]\נJ)Fr2 C% U#GJ1dH&H bZ}K%nAl\3uP!+*tyĪmXe`enib%%Ua?sdl(cx7Aξ+Gr+cLj54U-ܩ7:tmrr2kGO>?-, H)*AkpRX0qIa!%.1 \(<t: 7/AN&mn6#C2r >/}@db Rƃh)`跠VqLCT3Bf7!#  zlv)_9^?]]}1oBiWowL Dɂy*TVwEW{;rLBPa0 TUK D3L'h_ $ $ `a0FHͭ 5uѣR4ʪ>Q)>mn L 9WͪFe-a<3׮[c` N!aW@N.u&|^:RSbhuVt ļ em0RnV@PR䤥qUM8M&פlXa$0pZLQDY'gOXl67D4VyB!|^/]~wZ3HAf$uWx>j()n:~?6X6k/+ <=HN %<&V+Eݽ-;˘/Nr2rRn[IjbUfdMu $ZX{aC==V C /9Y =@$$mɄnOㅹS [hZ7qȨ8~0XM'SP{c 1QZ,w"q=N!9kSTϴj\Fh-c̒gępsb7ww/lvlǐ%tQƛ_kBBkzTTq12pȜ4/чzq8Wf\b$t2dn2#P '_lq-^c:\10)iB,c8;ƕ_! ?g ӇSϊ 09N2X_~ qsޘǜg=9ϫ$Q Aۥ+ŎeE.ĉI7$Xnq;Kl+Ze z! v,%$ pw]a3g`:p[p`7Wl{; r+ng' `Y&iit`ivT&b`H8z48G?iu RR?<6瑝~ǘ _(MJ6u6d a;F9q;'w|C㽘fm 3x g92IЅUBi<=ߚ~86.c?%$A_ma;谲}-l[v(JTWo똘Mj73򛨬đ&R7QWG?6pט,7)>Lx͉o#X=r'?*u VNg|7}&?琞i={̘#VϿ>xu;[<E?O~doZ\=}.T1{(ȯg{_ci#/?Kر_ĩ{Qxw@x?F?5n'FjpljoE)鯂yC' x렝qkcvD5fe >"޷iM^O;o} a[Z-fm^_+E4"o}9|3Ze =oal16E@ ݄ 奱̉#MK㟿@=7ۋ3{-S"mwQR1 ,B>}W0'O'Yd!=+ Z՚=`榦PTޓU((Ś6 u,dB!UĠ"l,~* U]}:֌c{ϼ %)XDMLDfrq-*cc[N}>?L`FHD{$Jb=Kssp-,P@l! ~ qKXm91{%*), ,$$c?!u\3T(yXڜBvvcyٍ^oۆP^G,E(3& 5 r,/`f6z_h1&!FFoR)%HD 0n alY%&d5Z dI0$"-Ey_XŠ)nv x 2Lz8LH`h*D V#O}d%~2|Ȃ SSY8/ zT`_`X_шܢ"oD ǃ&L&`V! 1<+ 444C8szժDff:OPQu Wۇ#A*PZ(b"=}vR AZU*pHM^D46T  \FG /=ۈllja{%*)FmM)Njf"^l(+@JW|Wچq}֙nT+cdbֱ}JQUB0 d*nɄ|da'l@Ҙ > aT*!"ؖ`0a%ڜQVY(`=) ueXZtAZlV!HDf;ãz(AŒñZJמ|E׿?ǎ4>]Ur8fڶ*{T_0ᰨI2TU\}`X2J7FtMKVihhؑF\:7pmtylechZF#rlddo7:P*ټ84LNmn@@{Gh NlVQBXѥ/_Wa!v}p;w""JISB>!q:WɽJYar_yxx>4Dض%JbcyaN'bTU@<A+Vy2Q3aMvr!dRUJq~FT4cae%E Ǐ6s=[ng#\)&rb]vVX)⑛ Xk1R"Zv//cn>BA,I>v{1Bvz*-rPW7QH.d읈78PjU^xx><̋F"#Ιϛ Ul -#AԞu菡j0dvFT] VZk8?H SX,H04 cM4* MԴ4`0MR60 ŠIw#"UVZ*`Ll2 ˻s#;سjU,ÿ>fg-|~ 6'xS^Gm$"D$-#l4uf)VGm,Vci,onA&HKcE_Lm՛U@Y(d9f2O%EpU6 AVe/yUd`2!5R"W5APQqKV줼wv }7"]S!VpiIMM#JjSՏ-S9%xo߿/O4}xL7V& n†_8 GH]/9sT!!ZP%&*_%-Y*Zź*~Ae'?z'#=;C  .?t{w@(VH.̻x3j5kTJa\M)G"'a_5VARSN;nD!D)೓gjsƬXbS΃uӬޚ4 VbI'24{#UAJD<{`9~Ǐ4jc7B+ae%lz=OTc6Wo rat0U4kY:2Ge0*k)].LLkBr FUƸ[.Í8s~cXPk-LªP&Cʂ#/6*DIh2`0J\Rao6 U Wp,?zn'6đÃ@CF$ Q]W\jڛ@fl6RS[XHژѢQD(똆 ubNW(?LXZ臇:I R(`n21 :{\G"x,kr?c6Mr9ź iƹ}4Xll5>CшU{ZLxp=}/G|Tbẁ2X F՘r :+\.7}~?k:זQ:Q)Ye :ұ.̞*L9fH;ʊ61WWc0Űd[X )V`Qb"ADHr1im/ELhhi?{/~`@n*|#'>Wt ޽1dp$+*"-*ăǹ } z!{ > ]X]$F;1 & J0 EIXd Ja7qQ7;yg^G,UWlQ*蚖e,,,%UUQSQY.>v9l6.~uppX mB*Jg. ÇqDժVgDAJJ~=?J冖 J!9`4M& @Imm\S&2sru+hNvR\Jm{3L*U*dSP. ՔT)V!`.R/\gãȃե9,΢x\x{!q`L?}%l#M8{/}as6B<";h 47PJL;Y@y,jkJ17ុZj,$)!w?_*Cobbx)VT$ @x _+De8<ɤu3v;i5T(!X aMvr*(Q/:&> 7 PPP 2q#';7&X.bPYId#6S#g`0VСrF*pHOK_}Qf_y˫DPlF\dwg45T/+m%v- E5bd罡l BA!"tnZPQ8!靇rzPpKiQ"”)45= |sO Z(+ղz6'F8v1c$2e qFzynX4B&b|–4!鳪fMpUj -*3%BvaH,lXta5D($i͸UTr&*r>7X P# ;Ijqhãz rafsDr9 (kl6+E8_b7q€cq|bV퓳-܌S&S\+VtDr+h6p2JǚAVUnN&8|huVJ!* i?DZFk4BmB1SJ'\倄`pU =SK IQw(,T-Y--ӫ\sKxkQ;ph22Ң>F"+-łӉYҏ}J0 \T2K.+Fa5NՉM 򳑙Nߣɜ|U!ry(kh -WTTB6,aG$x,HC^n&&?3Q+6B(i8Oo_[c@\]MY6@Pka*>Ѡ斖cգ{,.!p8fKIM (-6 `Wo } TX; ##@C]cGF@%J@;JCCC{[1c]?{=euu0΁bRMź͘!EU&=R׵w*e֡IYY-, biyg߉˸6O忐0LrJyY8~/v :ljٹ%V<qu5, |cM mydR!,F$JJO mV{yv$WHVϪJc(j|`Q,_KX93q٤#aqfKsOkF4ݹ#Jr6đ&}r7^Vדr\noFG?uιf\)R6)<0vlD8xã`EVihhH#;l"J0FaUHY•l~z=bŪXā6P54ĬXu,`DCC(U( ͉2>{`EĿO_vb_7,v*Tzwh> ,t QXMP^\6XLi  ӏlje 49ܜLƠ u L C(~Yq ` ^y>L4Q_Z8Y LuHDx:#SsXq{P"v2l|NvOO NJ{̠ybwDm 8 .l6byⰄ2 RZ ڜp>taTi9J52UC!T-H.RYv{155:#D~VGx IDATXZ\;U$uc0Az{i]?i xQPVWGYa57''5VۍVgEyGYF@%SSe2O&j| N+>d>BRSqNoǎ"f-6clIªA*,Q[E΂=?Ol^UUSXd2x|: ?)J15=b]bFpa7cP*j3sLmattK/ xlh-G]X!ax<6zW4.׃1kJMaa< 0xݙp+}dLIgS=nqq% ޲ݾV9No?ziS͜y|yJ(luIj嫃6BPPݩR -MiBh0&jxx"W^^vSuJIP*M(E6AVIrŜ2/RmL|61&utlu;SmOã lj"u L7 2ީs/^e2 n >L9l6a> GlK7l6BOWŀyjKgWl]C^Q2srܮ#e@VihhHđ4Z 0ězL \-CCWA.& @immvsKYP6e';}[>p~lolh6PAuI FCr/DF7ʒZb ¨ YEЎS4%jBAϪatea`rzseFJj*D}Wif*Ko?14,fA$(\n`lr)0Kϴzy.!ZF #J=,4==1klW|L&/vWlkaᘍ D ߳WG6\pZzaE F"T`ZQ( g5=V`(ҔLY@XeF kMw7m旰 A3d@J 0 eyYxKߝwPׇMީQ ^^ƬqPQL K5d--غu2cLz 9A ePT4*_)'mp3VPڌr<J„/ZmNW45A8 A +)sX9GXВtF5^l󉏜DΙ|ϯo 6&I1df޻.wMo/1vfiyzUU%8XZd䠀%}m#gJCCC b|k#O@Z؊U XZ6ZIWuuA:3|*<5{+$"& Glimmnjː7oP(9DO܇y<^{gG,F@~[BgǏ\t֤W !ݵ c[746*8@` %jݫB:XA%8v1 UR F "2/G/Lxvc<5zJx߰Ѻ 4Lľމ >#5^TƩXS_z|: wEM)FcT9+oh eq11ڝML`0(*!nB TJJg# :B:#Z* e;܈w#0(O|5WY]#"UYY~d$*xU!v{171VutDO{ 6xl>ۃ& x䁃޿&l0q`oMLfZZ^AсV4Kٳq e ^Y~?%EΈZYOΡ$UCHw킚`y$LL`͋|+ë6rLYpx_*H^j'q#ҳ//$O׷Ʋ!%#w]Z@ FeLNͭvOOcafRdeo 23ӯchx{B|e\- մ4~uuŭS֡NQ`2(Qo+ªJ n/ ta2ڄib?F$Lb'#;r 3ImEGL-0 2*b ݵ y}.-`Di[[_y , v.ȫDO^KZb|dI?uEyw?.yhW5vN ERH+:&wLa_Q"6r QecqE9%;D@ A)!JE0[ #:*Y%rARva&\:0Ńd"Fna!,Ⱥ֣TQ%+Y|ryl|/_Q",kMzYR~Uzz*$b%|@Vihh(#-jp"/J iߏiRRnBuz  +/ XLj){ըK7: ?;x/ZZH9O~Wp(N} ewc|`^'X~Ee d!q/ݤG]C޽{U "Y:99QW`067^mo|߾/,Ixc嘱ٶl4f ';1}^o D/H> kV&' b (J[k-)·R#wPգ=m+)Jɏj䒸."==5*+J$XYZ ))L|C  l](ם"0:jJ14=$̞&9q g/!Ռ[P!jÂvԕ2UGduWoժVmޡĞ567yK+ؿGLDʺ`2S?͇I&#a#됵Zeޞ +*J_5lnhfFPaNZ!Gyw*,ITy=hzzPgO(* a2-JmB n`8խ(dµ i$i?,_$ @Xr10|^愴R:!۽6쬌5+U::Pںw\uΙ{<9l6 x<Hz22`H9kRTH+V7BbcLmg-{BA!,ZmN(Q)r XY\zLGYZ)9l6QۡZ ]qW]p?$"05=L'_5DUk+^^&x~陙֥u[~8p׮2geF2*/4JyUI9X 444s`o FUL;77VM *)u j#gU )ք(tzz*90[''0Lb};F_mCL7 1M:+Z214DruW:v{a썜HvIޝd%,n< NqDLO(,SΆNV//pk[ Ss(wd"’>v x\649RW@qЖGl@Q,1wQ{/]jߑu6.;:HOK7 s񓟿 BAw{y^< [GopfP*pV5==yw@rrs312g''?z t)TCJUܫtTIy h'l;R!ݽ#1MXY^2B-an~)izt:cv-f*d&lvp7*j͐E\A2Om>/q6L\l._1槧a !<֌w+8gpxA#VLU}\A=`%VhY)1?" mk9y4(=a|ڌ*Ց6(uѐT֖ ^.`2aRE3t2j`0O}~۸pdc\gReĿI8&g񭧟bEغ;`ãz(n>0 4֕=_琟MQCf uuY=z'^z*۬jL^-g x<ЏD=N߹shm9r 0ޗd$MiHPwQ!߻7Uj6OgEljHD;{Ny;x?/4~[ί+WVv{w͟KWA5Νµ룸1w ?y ^Ic#W0>nlB>wei qZvͿ{<y 9VJC `jZ#Wݍ-dQG{1q n):u oվ̔ؔ/J!uM);7.KP+`v><.6{bb]6r>b.3X\t_u͟-Lgܹ.!4 01<jQXθh8|8C48 IDATKPV_qDXfw⟾JgHSs8v8d2Cߡw@yBcy<^huVTDuwSM\Q_8g G8v mb(rX˅ Ux^}q .ɩ9a˽{= Jk AvAliDO8tF ',D(,(ή.wC.gR阏]dakoL6<+nicW׻ (.$Zp8vnAlVsH3 >pm^oii)8uW 5WJ%kxyT1YsEf(j9apM ' s|nɾƤpӿC^Q>.wc?NʵIKշ_AaaFT_ %յĻd!7DFmdF#f]0-0uXb> /J%ofeŇ^K\ʌʮ7@*]LP= ;MZ ]rNBϢ@Wetw{;e0W_5ؼȂEϝT?v W'3$2@x9%ZZxڽv!/%8q%45qܼ}qU/ g6/ ~]'v%*&fp4g0ƹvUXS.bfn z&`2}U >̖H}Лt:V< Ů28 d0c,] i63]K ˃  ;f"23; AV^]z`0ݭiXtUrE!iHE .dl M Bbpd yy5h]aC5& z !ϤɈ4~&B-\9~ )Nl4-C,Imz!!1YRBv>qXO d"Ҟ33HǴsi}>P{ lFJ=>/feƈŅSy\P-AOHwwnjEx21}b^V>"]cσԌMJkV44e`#u'=Wt#'7xoJ%"XcBв7 W.zT:,vcH n G ( =H$klϔԏ`/ -->?Khl,׵k R FFѺ7s)NO^& U 7ǀ.IM ry=.}~IR,Ogx| Q]]嶁Λǧ>2BU?~c&"Jrfs蜅^pơ, N'iʼ|s;PAK/!U؊ૄ-Avvy 5W[,Pq֤|kx~H||3'8: ?cqa*!1YrasSS~#dd RDUU5.}dfrbjzOBvT2 ESJ{!}Ûo'+ʟKxR6F{NAAۄX ]c"P$o~8?)b4M?|")[$JR|?HяrtoIs]amVkw:3yN+B ܰiSڷ^R\V6knD~nDGȑzk6bۏ2YIQ\Mx'=Xw 5[:Vk@ǎAڷoh 9qP#O?0_B̍Q(U!xȌ9yU]>};F?d7wgLn?2LY/ajI LsFDT}X, ~ D:ň!끟ϛ5CuGMm6~q2ywJܜ>3llݽu-wUM4C:|AU NAeԶݸL:B$l[{{8GT bN'lo&F e6lZI0~&;0t%$^iJl2Gjl|9JH<~Q;,/FuPHqbDFiY%>ڴ혲T@7ުpIH *%J;Vr8?/~=RQRR? GG{,6S qrӍ}DDap;V63(Z:/4m@UBcz'R/F^a%-]}6@]_uuػ~=ls(+0Q  +ۼZ~w2'xiwL{U\ts?~v]C4c׾sPXYvg{qys^s̟\jJ+}r0g |曐gdc-(`{O!3[ CYy]"zyեcAwnGJ5YX%0f!轧.CHL)S+ϔ‚|⋰”tܨeNťXqlr'ec`xf._۶m(-(o|37q"4?ԊPǗ.W?ˇCV~ ?X˗;A^y;M5kZdduV!JҥȸA6 ]LxY][䔶ӣk5VY,۴ tl_ Ueeh4ȸ{߽o?E']s=UU5>^+߶ UW_bSZ*W9F”_-\[QoeԸ{[Z6>&9ܼB\auV++K »|Yzzb׺uv.%%C[]YrmcQTT.\ccдaxRiVVx}8"®>gpl0y|?tC%;J=Q_nCq+>B;*]7n@]WZCǏL0UBiY'娨UpAz}T^}Nmys .r1XvHCյn].F&ݻ[%Jt"^>.nXg.8ϗ..Xn$sKoдi oق߷l5AΝpXVs0)b'wndb8r`oFuaaiYoQW[ K++8{N[svdbWp7̍+Sf})N|N{gg8xl"_߅爲*F MbX8wњdB᭷-X鯽Tj&vwE S(U&\G=驧`@S_+^ݺVfaᜑӽxɱz<c >y <Ůh~~L_6ddӎEגxOXa86L`sDTz߶ LUW#x װ5L5V !Dlܑޡ~x|R>xOq읝y}gVkv#=}Exi߾4Nɩy:7V ? ٮmaI7z G8qR/pcO(6X!x\;2P'>/} e2%N5e啨5q"=Ұ0XXY6L<0bWl|r̟5lV*)92HKk:#Hf|UMbæ;Cztc$|x\#GbAWT= X*ݻjQZXKKScXlN{5= o/oXVÇ[E5sUյs7>5^o:s&ΜJlc3:Ba@Sv7U ;G oatx .n\&4-ƌꎡ$b@G?l9:"x jv>={ꭩ NN"6K푖!kׇmsb舠-g576ElԦj#!߹P[[G"pHFocgMU!NB(An~ ëfl2MFֿ2|~W[0%$HV>τISfy\yJ X,8u_j]3Os ~zͧg^GA=tctmaR\guW2)w|US.rA 4tuT ߱s.!'_:,ˬׁυ'oITZds~kÝ~3L_o ZXGzApo5B\e}^hغz56o=\|94/9:x h 5V !DϤHI͇ a4pp>#KnkKaQ>޴UU5?O/zc2,HE&E̍dW7=S_{ft>B^sh*g;j~*t)&]cV:6EFcΌpDE'0]j z f0Zv{zl #e땅tYmdcmŏē%5uWY~wX\7^~#Ĺb}Gڇb^|\ǻ-V|pU#o&4VW;KZc +ϺktM=xl6 '~aYM|U+Bzj `kc{;[#WF& W.j{ǂ9#iUO+<]LƪB|U2dsu׾s1u08]d$SШG7Ə ?|H# 6 b!VEAPcB đkGG;FW5#3KnkEJj> ՘2aOs$"lrLt+藍^g( p86:6WH1& *U9L'лӥ<|yqn$\ a!wb3Tz9i^5VV}BbwKzoİqנq J!$P@f#Xc+VrX>c䰞X8wNϻ ZEtr- 6.M+)DuM|ZAe b$4Ofw2U5u8x$ dN\ K~mB MUؽAٸF+BˍbǠ|{wIv&ͷHC^q%ƨꩳ7ነ #M @ΪFEQq\l19,5u0 -z"J^ϫVkpZ"D@UBR m]ޫpQ[^5?…8߯mBf+BR;de+%0@U7 6>]SqQ"'6L^ _V. k+[*Gn^zxzbämȔ*b'BC,%'G;&"M P;5}(utF"ˌX+!8#\")~/DU;zE , ?U5-g]ȡ8QqC  3K :W0۱:oތ@:^g=/Щc5(@\md,9aZ7ZnRII6~~ɕPT2yj ljrhGuhNvruw%&aڴ•bu\QQJ苾)u=87WHFqcO$ѝH<#7VbIǎ_A~,--玄ߦ* JlƱ,ƭTt֮Z>qd(ᥧߗX ]/"*!HnElPr u_HZ!6=G"'/U\kSx8_㪦>݇< DB,,,PVVҲJJ+QVZ )yPT{ 6ptCogQ;y@yE5dV,g#Eq&Bx)ݜ*@}aY9Jܺ/7>gkŪr)%R*tmqYOHqmn*fW9ZZZ/n\&m܃yv, Çwh7na1z4 ;Xu) C}Df2, )9 -6\+DvV*|TZ|Ya6\jLBr>Rr0vd{j:t,U X75G}52@qWcdc_cCj=nl2\x;^S1Wހ+п`Nr|7%&eG+W4xrlur>C7(,oӹ5;rT"{?XKq\|*,--Ĥ^(--CVNVKG.G/u=Î ۽CY~6dm#:*!Ȃy2 džFfT"a[XvT[^B]U-9;G ^ג+fu # r^j3xiTl!+sv{o/ۖ_cWXX' wA_s} 9\GQ }Xz Xu ;g,TcqfcrM(}5B $zx>x+W򋐘"mI`+xck}筽Ƨ%Go7Ejz>{,#ŕ=xTVY""#UEڢ!2V) Ԉ<*^Sv?93MS)"zJ 0 sիz{GK o~Է&#K4ȹrs# h4Zlz ג`owߚGdXYZ /LmZANLQ{pͭjTID"'_z5 1u͑HȃLnduu(-cps5)%i06'd9r |o`8by!+GCEEu{J%B|b6JJߠplXy5V 1S,@.G_Ɖm/a cO1qX|znݺ_c _ѧ@mXd|jKl7Vx1F~Cql[-1L!/L>p3‚>ZWދ E$Aaj{9#A3:N<#5=N4,\]nf?1VV@h B$׽ZWFyy\x:?ǑkwẆ 22ӽiO]5Iqv#"*C*O!F L55u(VÃoO>2phb7Fŷ{[h+51A}B&Ȓ[h䪈$^|ŨEN^<)$]zmѬ]v"!2#) J(56WbLC!@Nnjjt:^Tձ70z|<˰ja8N*@z}z*0jB,(]f3 zGQ̹Xh,Z-x"+͆Co"Dm+&|Ufiio)iͣJwFjת#pH_pqQ]S*#4P j2gXYYG$rV{b򝑒>os 5uHːģ_V5V !H:ge+\[8:wp8;9`'pmXa(u10)Hi |iŪIOuN^e8( 6xPZFp|醈><Ŵbv B|nqu\W.֬ZRra5WۊjXzC""c1d`wTEFUB1I)7V3j$#KOq++K~ @UR66V!*$PURC3SƽYݐ¢VQ*#5V !Ĩ OiNVdWxoymsʪ˞sr˱ixP[[~'3” 0e - _wk/NO  WCHW=XVrV+jQkuڍU!Xq/ʫz sdmm!q!n6 m1jBI<Š,6 +}q?> ; nIcӠ*ҹXZ'^ܼHL4y`H - QPTZs-G0wpxKwՐlaeeWUbiY 6? I,[ \p =C7EiYAeNF Ҹ#*!f3&7YKv4VLkIȗcwߜ@*$ĸ"Z ;G !֖ WEy= JvΟwcF9I|'9w`+2P >AdraĹpv=\%F`3~@brN$Ɗn5V !Ĉ,,sGJj~ge+cbiR9e#-VUHqu OVUI3唯Js;xe⹧|u/=7صl2ڛ#W ~^B@/g6B@+VvBH-FAnz;>IC@UB12qj2E1b7jYٺ7VkjrO֬\?j<3baF~~3apWBryXˋ})plPх >\ir|կJX%BʂXm]a>|}Dȗ Wf9>$4-"R J!F&7&73,-y3**/_eU Vہ¢R8;cgGR}- ib x;b^ŬCC7?o/W.֬RB߰+1wR13娫W7B870Y9ݤb( JvNopsrGP7O\I|䱻q9p?PcBL\hJȔCbb@ð/O>2[(+Ff\.bW#UIq؄,Qӆ=xupҎ=g ps¸1azZ2cFw"!\@]ԡsow\r B3X,=GH̦* J7plm߰`=-jz#/Ȅz5%c`+$ IDATJ! {<`y{ /Bv 7l6￳nF,aB̒P EAI)*|ӟxn~U q R(US*!zG~*#7-< X>ܺn:L yFՍ[ɞʺ.jBb$=%$^Xxhc5+GwBYPf 7Mbo1]!fNv((,i#1m0|"SVrEÊUBHJW+ J1+/LPS[O<ȫ cF Ņ""hhJ! x UU5ٕ(saS](Vb7L8* 1.!C"!2yrVw?DŽ*Qj%WC$:B[[k,P(U0|fṧ'`ʄWcGqMFk2%vܺ.ˢoo/%xf*А'ȵsn:m܃j释*`Q!= x)tYkIx_(udBJ:B̍ T~X,y0oh4Z|I-La86'{vLeQcB`a)\K%.EaQSSQCaii)vcDB!fB(}t8 ŲhP+tb|7={b mbʐA!q+ !(IX%u W["dI8}&v jL8{A().BihZ_W[bʄ{2OmjjP NÇN|իLPĊ8 8g#]-|HJE>LӥQcB"#)PtV0֝4y1} 4z,-B9qpv<2;+]Z˵+$|X[*M c)s=Cɔ 1 <6VLҥQcB" @Jj4-e2(Zo1ד/>;'}g1 <ݤb+$bng&>TD^NjRjgP+BV%DB$KlYh(*.3ف݃ dwG*`,7^y2^XT?+/Lׁc!6JPAh삃$oY-,*=,Mx7^ ֬ہ|3 1jB8s&&c nܼ'G;Y5^b7'̒q& &bGW.l&>A<RsC(_Դ<׫\ezK&;cE\u 5V !AR1nIHhZ`r ze nb[Ͽ> _wgLB!fV]5M@Uj%WPcCpl.rAJZ>XvY1aPVVujL"e. i@ctrÆ|3֭^t **d$bDB y' "b)W=<*3[au1݃ "rtfcc7_1#{}tY*!0Kpsq`"c}Fݰf8r=>u@_Nv-B8yez1]!f"!)yzexyr9~3kBaQ)e3GUBʪ=x9x?/eB!-_s秀ͦ~b3 k@,9~g3]1cX%.*' I9psuĺwDX"B·KMm'qsmkքg[=B|*x"f2V ! ~+EUu-fQn!coo#ěR!ĬMx'ww©o`֣Q`011!X%.(9pCg&ښ9 bz֯]t %XYZ@tz8N_v"_DC7iB"yF`AtǖB!b& _n91ȗ̄"2V ! (,*Ú;q)2[k,uBMUB!B k@%yg܍`,bJ!f.>1+lGZ^{x!B!h܅<_} ?݇N\a,QcB؉S1XqJ*{ ^|"B!plk31c`hZ{_}{55uLF:)X%3TS[~tYB!za㺧e>EfQ!tBuj^x:p.B!B:-G^>]#pe%fՑ򈉡*!t2blri2XX`HL,ӕB!BHb0st`ߐk㹥пo7#&!9w6Vi2pbLOMUB!Bѷh>RWaCc4b"h*!tN Z8 "B!:p5{D\b^ya*|DLGFUB1qwb3  Y8 tYB!%X1}$}{ Y9J/0L4l6m!쪨J!&1j@'^z~2|gK#B!.Gdž`8W4w9h>y.xxP-"""""RԔ)3#]Ff)Q1yyWkٓBѺZXg`CF>їF ;k""""""r6q|26Gշ^݂ݝ5q1VED(*3g0+A*UBy_F?sfƷص{r2);{d2;)̞'kn*7UQPn&""""""Ԯmc9+Y~']ͺ1|{%""nv2U;wqjdEDDDDDJeԮY+9xoN[RY'Q`UD:|.#v!5C{Xŏݻw9w""""""L D:$+FM1ܝC)(VED\,R* ~ZjQB܍[:!)<= t;]:5e7K;b6'5kTvwXq֬ɜ9{"F^w֭""""""Lye̻]fңk+EcbJUo_4jPIUܜ3qnҜ6 :ʒlàLFwgSJ<ǜy~ntDe|pztkW.#& _}%+"ydHwZ6,J)*"/ӯX2T fnwv"FJLxmq|?wG&ѬIUw< @j+#i&.^`MxpT (쉈H׮M#Zϒ,u#Q13a&3ۨrSEwgQrH>X6V?o yU|1Mӎ|nȍwWwiww߯)P`UDVuYzN: @:Uypmh^͹⬌7 JmXznbŪmEZr]P)XɃ̀O6riW `н]hST (rW[/V][ѿo{*uw6K=VEDrffN~mӕ+Ts_Nt""""""""=<| %K#XVv3wiOJܝRKUl\f$3g/W{W':whѨZ5*sq,u#af-,s+sU&wgQ`UD$SX<r):poi+TEDDDDDmj׺Ñ&6la6lyӺ;iBKVDjY:^"zޏAuaVΎvD%PFezц.h Hur7ElV<dKݎ:9"""""""9\VƲ:|/g-z֊j:5X!TPgz+exsOէ#bYҜ6T)|}٭=ᓬZ 9t$_M.m_Y.6Xb/%2w&%ȭ{Izuܱ kT)jը̣C]پs7=9Bܞ#| ԯF6hۺ!ܝ"MU)Md}lOX+թ]!AtlD*~n̥Hc2iݲ[6rj[9˩ilߙ_[e^8,^F5h֤͚Ԧn*LFwg߭X)g™>f"m9_y푄T4x`9z,Gؽ0vWS/77Kiys=ʖvSnEDDDDDDJ/OڵiD6?J䶽J vFgg~|=iؠ:kҨAu ӽ)*R"]&r$V~_4f1=cUujqx'H8pdD : ǎ'#*Q  PZ%թJU]&j׼2%B(VEJ"6Wk$`Txe{mijrl]ƦMXz5գ{_\2'Oߧ9v<ǒ8|4X׭_)< Wq5IZU0Iq|LS;=?J>rGNvCԕQ-0@*~T\ʕ*`6_ʕ+ '$$;#_R`U$DReUM?Vr:M`5u 6z)6o|ev1eʇ\JjrTS.s%_Hً=w8}<ϑj6T"ujDZUS VůbY_ŲthDvAY87 pБS$>OD6 ?W,_Y*\9_ʗLo=/O<&6/<̛73}B**"9ZfUAUp?*eWw(-oo*bWh';%_[; %TƮ2v=멌]Oez*cSJc u@v;gϥt>s3g9s6.qN>9 px_Ufv쏓+VEJ"Seq3b;~UСlX.FɈlaaieienj1˓gm)$JS']S"""""""RtxU/`K T ,Vi6iViVl6 Վjnc\6C90\IUԐN܍1.lhm`5uԫm8z0}D ;_y푄TWnyeߣGzke-l~&i/_2WXXW oߞ|0= `=ɉT~1VFzcbv؝~ȟG,Q:]8Tǝ.=hK\SƠz,y] Zݓc46zL91;åXf~;9~"KO9r+cP=vQyrƒT؎}cB ȍet[hAvY{Y>Kfl9iy))Tǝ2m-tgl IDAT.M;O![7C ~{r*cコ'ww"Z`c'UNu2V=vV*'a|a2xޞB*GVE2M̜ŧ2UOrxv('PY8 u4۷x,&£:^p~Pu |툱t3o=Kbmrwr,K5`HVy_ΓY\ 2;N[:icAk]xc]A)r+c`nNZSxkxSUoFOLm#$@T#rT=vXIhD}~\vFL|zT }NLRD!44۷;""""""""r-Zh*G)*""""""""" VEDDDDDDDDDXq""""""""""R`UDDDDDDDDDA 8HU3!"۾} """"""""hѢEz`u΋\#44Y"##ݝɆNz=cUDDDDDDDDDQ 8HU)*""""""""" 3 "HPP*b2\IaKL:î(;\ѬY3EDDD KG J2/h?;vk*O%.|?/‰4ܚF\Ynđ]X`1ѧmo\U"PXX)}CjzgOlf?gC:/Esx?˾~!00m1kb`d$Qu WpUDf͚1 @b̿Iݞz:WO ׭԰ H1yɒ1R\ z0N{>OR'L>kJy8ӏ>gyug^62TCOүY~6)S度g>-όH#35*9q@&͘jc /FC|ZצDV,v"=@:S:;%K~ Jfr0?Cnٚz?D}Zs$޻h^5M8+@Mh0Ǐ!b'@$}܏W$vQ'elXrLJ,MO&jG/v>z}iO-xk~ӼZLa[*B2}9G+`f-s9ځ&goM_'+6_r[^|ݸz uAN='-m?|ȗO5|)).B|c۝υPڒV1|Lzu-xcqwxxS|055??1q94 ʛS+43!nj Js%intKߣIguXp:pঢ়sŤ%xנ~+uТ_7oqyJFYMg bf(nn9~ ]W;U.Xncbb'C3..Od-9kج ފf> Y>aszuc+ {yeZ~0ebZ7?5`y ni!K,'뜤ק𾘷 hRSۮ>[jXlR.yNnUh"rc:VL^{҇Wήs`}:}YS~1E–4jlfqC|~[>YohobųO3;zj\Xm+5g~_> i@j|+:(LmO& '^X'̴H%=<%m.1x۸tٽ,^x;"=6#QV~'ue9^ XcSwg,ߢ'`'mrgI?g2ů9$ocKCj&ƅx@DF xӠ~ |?KRiBڳ Mؼ&KԇT?IHW2X8|mvc'x~$N^5 &LY&匓f34ːykMέ≾_Л3,V̒Pxo%܍$yt(њeSeӼ64**śݎ58?HOө %LC8fm c[AN]7m'L=/yOIsU_=?̵AuK6%q~>({m v3sS1F.^9N=pM:7֑Ҭe7q+XH]8w֖[YX+C[Ⓝ *ge៫ds߾%Kxd[2f,]^{F󚼺b}>vF//l/g-͛("ycHS.q™ yK߃Cxբ[$*.fX.Uؗ71AOԈG0;1nn4\L ctGh_f5_S,fр^]=l3.dD+~?el o Fb>:͕oÏ/P_U)RBq,[YƱ%%7ZZY1V8?׵a?Mq/qFw\Ĥ e Oo%}%-z_tbƏy<wn50_?i͟?i<~qETk*)A>|gS\Ŕ}xՕNa*S:b\iGzĮWb28w֟~EcY*81\YrI#`?&sB4xS@L'nqpEvr+fZ֌Sk琱&M&h|Ck⛯kS̈́"R1ְ~wk/ ^vFY!]G#\y\ι^yu2suAt\?$^?ٌ<_: @DDDJ}ҩKdg홺9z.W^lWn7բK#"-ף! @j$v_S<$53N' O%MAz._~WzBu;6`.~})%~{)o]M=A̞T07{dVr _۫?>'g&;7?.sq}a ۴iD%"##><96~^-Vf|1L{{hl~JݩJcX8u+$'c.S תsΣ 8@yµk.f~`n73`̿Iݞz:WO ٮ԰Ngq4&/: "3g n\J԰Ig#ʍl?$$_Wę5k֬PcfLuH1+GB&0s.l&f5y_+&!**ayjX'tMc%?c|M RDe*@R)r%YY6Nawl ǎswvDDp Әpte*VE(E3f8ޞ0rj̟s⹂fMruMT_ƅ}CYȘQ۲F-qeDC}Mb(63onָ;颂/6.mI5٭y/ܺ'wh@+51ZŌ @zeգaV?ni{SAH@HwoYNBv}y(Ō5'װ1q#vw^9s8E,ɜܹX/]sZd̤hRWqR"1^s-c}gn18>N'2i͐זxa~i8aW0oeB̶00 l-&S_FK^\ݟK˗RӟqQ~;7~|={7+g̘f{gq0Z|9NI)HD8y:ʹ;v[.t7*2wl( S#X*eb,[s,ݼ]3JtlW8*'X' \3~*90j7> n!\,Kּ2rMł5%g0ZUř|} |RkԴ ƇV}ȃ |FgP; 21p]\;:'%/cY왿EN!a_4Gm.)BeUSd3iwm:USq"r;,_1|Tg횣7!BMAX/>CP7Eh[8u /O`<xGJv T%鹲)%U>޾ԭՈl,fnkkeٔ9콘c il(C-oKbv*/d˗Ә`wj1ykU0ɛ+a<¾H^eޞD|"_cE$VGoƳ=eֿK>~. 2"R=(+ҺMCH dKǸ/}OLn%|ztRnxe]CT.qJ5z-55P<)*Wuo~qvj+ v${+VNvPc>G& />)CC7INbʿns/GjKC>>l߾]ADbM9MK̴yMr,i*-n[j<˰Yh~n- [gݷ]7Ok,X,9xd_ P7f6U58t@1jhl?7y>5uMK_|cϟzriy^Ekb%:GحZc9Ѐ!uҕ6/d~ cԸO8Z)UMw06t.ehP2{bŃ&nyФ\XA{Կ} @q˘;3yO,:gЫL$h)siqRj3 js3q7R|tgLH{"Ɇ;hֽ7;Uq1EƺJWڬX+v#G0uT|}}9r{v(ݕ+WҩS'jԨQhѢ)))̝;iy/Q3nȆл翹f.@fW^q*lOM5o)xu p`&|:b9 CBH>xKW=`6c?$4'MT IDATjZ q+ R:Lyǯ\xΩ:$+~X1X{U_donc~g&|{vO]3ureL.6)9a&LcXy/֭|>ј}2p{dֿ:xl6Vv/SHP_F$?g*JUDdž{Y,dٛ>˙,M-9VUZ//3P&evU9\Ϝ ;7tiϚ5qѢE -ZDbb,$6Ɍڒ3*F7M6پnA޺ qqX1)Cن k ٱ=w9 ڼyѢE,Z톁ը;h׾ɺrUf4K 87iLŊ&wgI:HL.́#RD4k)hIm{M^ˈN3]jvcǎfo&I`3(fsmv{mĈnݿ6 0e%L&3k5a@zHЬY3fol`c Õ-u~AbFm{M^ˈN3]j+? 'UM`I(qw;+j!.a ͠WKqr΂Hڵs鼙t CzHpps̟?m&W~D}oH>SQOM~qw6 Σ/ʇG*ݙqܞQ3 H 7y%?F+3q3p֠u[M-#!)e9 Lh{J^ђASt]sr 7b'^9s8E,ɜܹX/]sZd̤hբ,U1^s-c}gn18GA~YAx^~1V~ CRᬶL_?2glI/;hRLWq*KϘ}͊ }C ym9&gX۲t3 }t+s'o.<ƌOG05?jLY` nU{wZw*ѱ]%2oc۟x.$cr (}xK|u []@Yp! BڙS.)?BX~=+y8d:-5+: ֔NGD8j*\}q&_0|}F:51w`ڬ]R}2YW1Ce2 ~ !wPYDP6;v0-cqw8SQ ~'b` >zCn@ ZX7!B; _mMXNtѕ.u1ξOng,GY=b؈ۧ)EG26y(^J淈 AUW[ ڇXN]>ł ؇π-P6ԥKseK8odnF|=7.}`wg '8q* su~c$^e =mM4M%pUx%MsI]ӾƄSԎ[ud ̇YH\[a|NE:$.{'."0>z_?5-ť/QqF4O0QW?ԗ)+G& />)CC7INbʿns/GjKC1TįN4{&lZXvlsJ+6l#$XlAMj@fU4tN!Nظzi;,3"kaQF^^.1Lxǡ^^._}O~uDJK;q7&n_Ԃhڱ-y'34yMyٓIKY_{yҺnǗ4FOg&w= 0`??fjoL1,>pc9#!!k:g}MYL BWKpֿR>s, eDJ) Mp_r/`u͜$s1<~~i=J?\uGGqϘBCCJD$/,X,ME żϦڳY4ϴ<&-5BOKk4?~[|bYz0bۓ>bB=%cq,Zw9/t,j&fzU酷dQ~}ލz}`4R~<5||c}0ʡSJM&7ZC,񗨖hCӏż?c>psgU2EͰ@rRb.iZeiU"jj֗+ܳ}ASDM5DY~ 30_{9w.s=wpyTCy'/ǧ)Mf&'WS$N"s\2-Mr!Lr|&TW91sp͋3e>VaorHK};Qt a{ѣgϗO%&Sc7 Cr#LUܸ&s Q_INK>l84~_6{{SPE30ЬX!*@!1>ep37*B{OώC_IjNRj k=ICusph gQI:Gtt'1Ѷ̍rlKr!LMme*!TqχJKU"?^GZzqHMMusXn NmAb7ۑ\F%9h;E @2 55Ua!%ɽcX:qB'9XTB!B!Ba"XB!B!B9>>~TXZ`K+F5O70 !B!BhǪM6㗅;O8N/v9enLf[!/mԸhcϟ_l'&'OovRbP9nc1{^^^4 `ن0NǢi^C: `DߗŀEr뽯nΣ$eD27&q-e|KeN\4ڱY,@a3>Cחf{eH8yDw$8Š?ҩU2y2l&88.\Xѯ{Z@!hҥ&o#1Ѷ̍rlKr$$LcN\,rUt:w15k=3 (sYI)%RU9ueʼnl0mٶn鎟+ںw264-q\JrLEiĉi'$e&&r2s$;ˈ|KX3)qQlT͙]:9ۺ!…:ϿO|ϡLxq~C׫eP ?K=T*At%Bf8ueS;ldRz8彾h,Xڗ7w>T2ch6匥ڐrs ]M>7aa$e$kH3hsz8=>ibZl_ReQɍϨLiڠkBX{^28b-/Yc|2#qUL_HܡF#b'Lz KMGR|?['NUa$JC?`b(yE0իtTF w[0WM}^^KH:)gסnHJAĉmxPkR ZvA˞-P`v\{1y r5&h33Cn,vY<T:YlXKY_ wޕ)C発"gg{'pHcB^ZBz֠et 9Rp@n)gcQӯ:7gt4/yQ]:͙6Ҷ'sfHqnG/lq`topKb^L؅nk|VVS )?|㮗'Fk4ͽ߹9,xzU.5kx*8=e]Hbw[ g%[w7Ǡ} ^ZUm)z齾?LMeQ;#hYaiOr> "yv;&0_ecԗo/Vڇug9Sb ~;1aA2QL{ odA˥K,;.^]\f$%a bHJr6. rl#ssuyjn (MUCq4c6yp Ӧr@@/ j&o RƩO^5O r܌))L4gLݜ`f~ކHOڷebGƾ ˕3GD\F@Ň2p!Z2B䐿?aMv]țfBMν(R8fǪKsf̋iucC;_ɥqQys?dqg :,;ŕۉPujJьď:pB[15=d_1 v9f0aZԬz-[bZo]́C\#S۬=ʮxIeU1}5z{g\TqRdX'xⱺM&;kAG.g3boU)>۴$, ajΰ{f.%qN;.dd0({q]o2pqoF!\δߠ9R! 7ٰU~3qʈ1cr>UՐ)I./e=1dr]ًOWaWyjݸ:|-Mr!O8]smFZQl}mKw>Yj x6~q]hk1z ~>d9OW'IW}綜r|wZ/$z=bU(.-ٺ x>KZ%sBC:o},e@ojT{:R?p.OW%RiAʹ.{cuOs\bfn1\ܠס1(֟,@qQūI۩\\geyH2XvɱLR/0ihclݜct}iH쭀;do1テȩwF4d厇CgeY (V8;gEM~xW FCu\>zHrvK$[sIs5'-Ky.'&&f>5e@2] IDATUe>s yNQ?a=Jg; PU|*@]}ʱ,prO'S m*gkѥ[ v.;ŵW?<}ev^,SaD?2:aRYwr:'0_};gg'T*ǜشl1'𞲞>Y2{ƏϪa8g%R6xq ٠a|6r'KCIqɈxufdz+t|:̭IKCyޛBh \*ѩj'\ծ&mͿǎaݛ)*.kҥL`!_[a]vd(-ҥK-V^ a27/`Oh$c 8Q9/uX4 ,aibJyයxbOաU`-hOH*ο Wdxqa-1켷GyUHܔ'nI;ѰE;G 㴡!y Kie ټh=Sc&8ZUr!rH%8L>S< >HF-cvN`0y$f}Gx6Jb& SF )d2hN~̛ɡp-{4}~)q{{ aܤ\q6u?}כXuYeZѫk >_fOXw}|6ʿr3FLK᡼ZdnZc09Fvm-*@}yy47z胢Ѡrv302GӇo`Yfe략d\fr-"qo>ORa'4{濝3]æaV;=44_>"1/:Chh8˻b;=>>OqThh4ݟ%4> :'9vٜ#2y\$ɷ8X>Sԅ<-&|=he(QС}̈~6@ay|W8丕Ja`. ;S1aaa_Ϟ/1yptE>aa=GGbƠՒ~ h33є+GݺTlEcuj0_\\s.EQ A1~E~׌ۯ{zׄ/s>O%&Sc7 pTo k|*n\ sBX@_PүSF%YxW#-ۛ`( f0ƒ[\9hLr2QI%E`J\ "NZ-ՖJg!ԯ$j\')ijWٺ9fp?ˍ=-VFbmٖ2I%E2'.=Jyt:<ȖLKr{J-SӮH]s~T^Dw:8Ijj#ʠhm($&ږ1QmI.#[¾H\-sюU頳v0o~}qHx RU{!`D՛*e$BI$DbbcU:lNT"FгG+=kt:=iW<!B!B!eюU頳餓B!B!BXW3JB!B!BK1&D)兯_}*TDRۺ9'-21qrA[Kːh[D9n%䞎AmcURˋ,FXt:Pj OѫPyߗŀEr뽯nΣ$IL-sc7ے\FCrO qѶ̉ұ*D)eH8yMqH:GXG:[&OFm܅KQ˔p4uz___(D)tRh[D9n%䞥39qȎU~,Jv*ϊHJz=Vԕ)epeۺ1;~*hfXTIP> *-D'NL;!)3135&&ݑ\F%{Nϔ^E2Xv*o:ueʢ''m /o|!iʊKzT2J`:Bf6sΏh{Vt),9Gﺑ;VKzd$h_GGݺT @ј~¸!(*7@Qc1gSLXLѱ[%m0&_55&J.c\WtWqNk;LZϭkI.#cӺ$)gÏONU!)q_?[70WM}^^KHܓLY4NX8UuGr?TT |&N oÃ^KH^7r!rh܎;{y/ fmy r5&h33Cn,vY<T:Y-bE;ZMo[xǍs>(Rwί 嗕kW\'سxVw(_˖~_]m],{KZYe4OӻO vN +uaX)]}p8{W,NBPigvc!wT4*۩bKN@sG{+N7{ #ز'JڼˢOAKx6oOEP*ӢYe'P!#~go020dIPu*Th4Y?Dz}:Ƶf{-ݽ-_!OzD[0hfjݸAzDI)*,E,bE{[,̓QCC7kj7hDݘlaO{z Ư|C_yPV= I*Ux:>|'f>ÄA5SǞk3ZԊb~5huegū +{ی$ z}3d$%QMbgN'N'dݺAP],@C+f4hTi InAKyqSxs);>=f7'enr3z3TӜao2us2*y#=i߮&7~F"3|*.WOk[UNz.gɼ~arSzq܊Fr!J?)2ɽ1z!/wν(m)gt#!da)յY([p.RFg]\+Tڣ:\WTf/QwWq=Ee'+uOFթz'KPPU1Dtd2Y~f9Ck-v[gK+l%$6}A^sL8UC8XO\{BV=7⎻K& ʿdx^ٻ>߶e3dB25C5iArVCjnag a!?c |48P!S\?z.~osSf~#~!B$a1W`RTvixC<FS iF7ƦgvXpu>`S}˛d12o0BK^%sBC:=`4-{:NdsKSU߂i)\(8XӜ>ĪOjҪl/4_Ј{4 0ZQv=MNRMKU~>"^%,X؍8kB^ i4-y" TX6p~SwǂRCcO-zI߷۸Za %2A1Y2{:g'MNH#~v%d.vRwNaH(xFų++hڇuyغh ]*^&:퍢.| EOͳtR~0sb-/YdeXt2od0#l"Ft"!9b>0є5* xbnb{Aw#tĞCZTlOߛp+F2.9CO"YIR̵t "uk~90L3X3%dij_{*nNd0g"OX9k#AyܼwFL*:)*4zɣ^)y/v|KѐDK!eXMм V[k0`5A?wc-tƼ4=*|sl=u׻0eڑJ/1n}-B^N8}?[ѲX-{^ OUØƝ8Ogw>keu[X'Z`) {?l/EFu7vcߜcds ?u,@h)CJ-P0}n@2OFw]}P4Tx\1sh0f,Di2y,ZgƲuZ2_ht~X؆ aOs;.yz*3lMiɹH};M ø5%*F^M|R-'ؿh7q~BC\s ~c@@,IIT?ɱ?""q0)b \FGrOr|&BO"i΅JTX6^ӐV>&YH.D/m'wJ`GV8:J͈~%g-j/`'> v7׫F>P<k${s9:{|=3ٴc%Ŀ.GסVkAs6V?Gi*mHB_WPwo"#!mf&r[hLkcUUK'eE\\Uʕ[(*&q%e]??b&T%W׾À{0cXp||c2Ar!䞥C3E]+lRgc`"{u ?yA4bN1!2\>D i,J=rkwZUj>p{;aLǬטS]: {ae|0}l_INK>l~MyCQ@FtcV+擘hLr2B7=E`J\9VBIOdNK5hٽA_F Lܟ2Yg S4t0? 9׫ro2^EsQpƷq km<¼1F2|7ݩ 6y@Qbqk?i4jWX*O |_XyB8"enLf[Q:H8$.ږ9qQș>|6r'KCs&;Y5L3m9ه\ qzv꺴ij y{NcHߧ^6;F0S2`vRټfoLh˾h}w;wmD)>FC3LN?Mg Ys.s{ԚW?_ΑX-jHMM`D$[Rʶkuq0"T[7GhmFbmٖ2B{:eN\TС޺߼n& 8&ZFaB!B!¶d*!B!B!0t !B!B!v:(W3B!B!Df'J:uj4Xlewu?/H]^^^S|yT*^ү^#2# !JD27&zyyۀF-ǭĥ_!6&Zr"H.#(ImԸhc5.6|$Aυ(%j uh@NC8ilޜ-IIA7XNqJPݳ"A͛^NH|-*EUU_U/'$Bh[D///6cٚ$NrKE~j:5f8PrBAE EQAJkmOI.#(m˜hc555tnՏJ+[BUz4FDHbߨc8}9u{N]Jckq7,3'#dZmC|}}-R[tHL-sc_,]sCKNϱTse*`wk$$3m˜XDe& QU(_3Wl c̄읺}%Lg2.: Sh}-rS.F'__y?SȲ`wW{p_! wtQrTv!D1|h3{tnQddd>h}%2! y>uS60@WsRg 1|5qGu f4C"v\ "g>/n̒%Z ڜcmf&WcbHL䱞=QZ&Q;;ٴ~!D2i|3~7/aƽJIBV#SCiJ  Yut ! ۷* [ه}>Nǘuqr$nZͮA֨Δmg1&ĻѨW1oF ?ƄpC h5~ iNg*OʎELޒB 3E_'Qho}.o_bE:U턫ڴna}uӠբIr6ldĪ–\\x{gzK9;Jm #Yv8 p'LxE V/Sgjc]yvy\uQ c+<al X2 *y95a ?fgrF.@NWZUB6&8Gr1j|~M+t}$NM8[\hq1my}E'Ԅ6Ѳml>ߕ5"#V6ʨ^ALr. I)nRє<;څSDOfhp`:v兯_}*TD2\Zebc5O%u9Z]vM]Sر1[!/)P";n.7%tH'|M&S8lFj;ӎ.V7[='R%YF.TrTm39cHJɈ*5 IDATt !lٙU;CMOm$TU%_" z(L\0-_L /kōYy&R96!;<~CZ f(&#VU43f3\6+>Ж4ZLxAL "j-jwefH+Y)P7m"?ҝvw4OкZ"c"#/4&UE50^4|:kᛏ[hIʓKoηY杲 #VvՅ51wnőnqiP3!hNz&S<~q3Cw"pD^^^4 `ن0NǢәvZ ?zuz'K]Re^>4iKS漠kT>~ +s4 .~_ &i$ţ.T۱kVѰ (jPE h.3_ιu)qܺN[}_MgБd =o}>QXB(θ;"&prY0 L.SnB-mG)6oJjO{tq.+}Ʉ t̙r4?_W9 ʭTT(SȣRs I Qͬmwfn9/Σ1' }ߟs vg-D`ȝPETůC3rfLbkA4K\Ix<•]?}:uK>\1 ]ԥŌ*qskS+j\T.tbv*YQA̱*nOڕ1θ+nY(xPu$ù:'tɟ؆l}9*VPE=.|>[}kSF#y=E^'jf@CFpgÇaSGfLr\&WxDR{TiQ8ufxei׺?֘ B{pf_*S~Ӆr nݼI=L11F{]3q7lXXh^~bH]ReT u>Tqxe2(ԫ|SWǾ I**JfC->jA#ǹﳛXI)g}&K%%c0Txx)&#Vx:[AyndeW!|eT"-_+IXBbZΘQddde{aʦ0aSrK ckuyLpGe7%ϘvMD o}yU*ev/o|N꒺.T z}i>*T*O j[-Px\\޻763MrxԭKƍQ4aa!DeȎbB:J=L|/uq:E1drx/9h.yLzZNgE{S2#2$Bm3_w[Y%Ʊ  \-tss]5W=x[MNw:<28U60C v0I]Re9y)v( _V}ybү^3SPE30.Ftƒ!DAv115|sVl36UIKbv5!eH\E/M8ȏ~SLPӦsɥI]Rb_]jVȨIaJFeOmP=EGG3wwס#DGG[,2jRFVQۋSvbutt4߭2B!qѶ̉fO}g&g$\eXgKrLJDx8O7lH'lrᤦӥM̀]-VBXD27&r0VVl(^'-*#\fp_\(\FQR$.ږ9q̉jt\+ߕfE.G4e.AN"Ieqjڏ:vh0]5s .K!B!B:f 9|>.,z4~x gKM꒺.!B!BaLXu d\+jўϧ ['uI]VB!B!(LcUD!xOz. s%YI7XeRhu ?*/J%׈9zDeEʑ9DIh[D///||Pè帕+DK.#NH\-Si{^FԏkRaR%u=(///7gKqRbmT* j a|-*EUU_U/ :enLi`39@ txPǠVS*ofe?s_ ܟ@){Eu{ݟLm˜hZǪ®7jN_NuS^oԥ42y2l&%Pj5:kDٷtRh[D Y e/:cɩ,_A $A1w៹n#s?seN\4ڱP5tjZ}[Kr,BIRbلƏ1wR=ءH;xop6h䝄w["l R=2~` CRԘYaN]bqjߛo[KJkB;|D0ʔh(J:uj4Xjwu?/%u9Z]d򭎚>7m8ʗ3cJcB{Rm %,gܲ`w(xk.MhX mImȢU3N7<\~aa=}׍$ Z##HLD{:ww<֥b@Яvv6y;!'Q\_a&og,&>؄}Gc~#1'&yS.F'__y?SȲ`w(TnڝޚNM`e7Rq:\f'A%7i a37|h3{tnQddd>h}ӳoS1&S5y=; `DdJ]ReKS7es $q51'u@WqZB_iF8d*PעK\_?aї1TI~F![l5ӎ;uZMd"͛YVAh33CFb"յXe3iLnE)aI~C̺^;1wFFaW]o),Xʟg \T\լ o=ۀص~2 y6&U6|M_G^fm!ަsɄ^?Q=M6'<; r~oNxoFnw3xa?sV3[.3nL2S!#0nL> i|f;,o_ª{1[BcQ~J3/6DxV#SCiJ wISU a3{bւڻs?P+F?D!vr0"έQ{e uq0"sH]Re;*}}x޷)9'f!O=DFHܴ]UxQ9No)ΒcLH wQ]-bugąƏ10\|ZcB}ęʓcBednq# PjpB2'ݩH[{^-qNcѢ^/F;,Dk-YT8I@g^OzD۴)vy2PX] [rsqif.$V+~9a|s>ώ_?IDfO_KfN|BK0ԙR<֦≽$r~~nmܒ7s:U1 wԁ Z4ar:E{F;ƾەgjU{*OC۾'a"Gn &L]>v\!ٌLJTX6ŐE G÷8yԵ98N`-f]QTw&,\aݬ9|+ \FqrB?T<*55:. u5VNalnqVVp@y츹ޔ!Y+o7WlɛAṌ>܎ILbEMޟG.\ñ4m,_4oBu(M >S8lFj;ӎ. KokFح5* yR.]}9b&_lǽQ7v c5huegū +{ی{NDz2cU؜] [suvz*|Pneg?S[7 TU%_" z(?!%r]ηʹ3pf -ĝFu+s,tt=C4[:5`LH/Z:+=Lpu`M8Ϯ_ȹ3%o|vzj Mxqjs̜UE_fd vJ;0&ddƧ 8WWpݕ!hf1cLC zGܴHwҝI~i^y;uA2ir˓Koηh!Lޞco9cdO 2RNۓCpsuvj̝5[txqd;UԏӠf*{C ќ_ o.A`ݜ3eR.LY< |F[A:\DCsּ4m KqP4AK{wU>psgEq5-rGr雘feje٢R[YesԬ5WDqYg(*0/f]Ι{yOiILHPeccljiߠ*BeԬwI@p 7S\zSso͋UY{:Mqqw:PRpS٦F%h;UNrdMM:pz s?S[`Ϻ>AEUqį((<ܵ#?sɔs(PJV:ncw~ℋs244%Dg1씬wth1ҋ Ƅ@r0c^>ԆݘivM U! |W Is98&ųfiRq\7Rݺw$W㹖y[1[$,Ror5;uU5Cm|VC@ eqiY4˶p56oY$g_g<gzEΉN,ZO䄚l~- S؂_ {KJIʍ5BB#%A$fuBN><~z= o/` cڸ03>ށO_|h굩.%^avpB#%>v!kDꂛ`̙KyoA-}MlȾz>1M"{2ٸ9|?d©{Ǚ`dZ3Q $8Ϯ? D7[mğ g q̕Ƞ(TުqJժŭ'LRrKoիW3חY6Z!r"_ɗ=dHLN.7RpR+Our_x>ׅ֗7㉄Bb2ih@Oչ~jwq́Wc _z+c`<ÿ:O3L9ȘQCR8Hxt XO;Kؾo͎;|~]row¸9Sid#/%g uޤLhld҇Wʹ7MJR͕0L՗_/ B|&L|=IHLzn^e@T>N=xeaiݢ"ƹÜ1@u1},V6Eā_F[U3_.  www<==quuE+T-qBCC-SXϲf___~ز;uag7Ȱ;u`V0cP +S/;ӤId~SRҝ;(:GGs|PQtU̔Q:3zWȖs$CPzuj`jv5f0n'Ӟ]8U}O_zDj*֞OX'UFxYVӘVRo"1;Gcȶ/j>:G/?ҟnӎD,u9=}}}mu8sfyp"y.3f ^Sbu>.Jz(Y?o]Cv~e5gf'd9rGwu\xq<3 9͂IXS:&_1 :,X?~›UggnT]|&7E4oޜ[jf qa:ڷosf?PUNAU_0ڻ#RTΙYuD ={s 'ObLHB:upk`ٯ;%x9vM,"'w;&n<Ͳ[h cڄ'aL9X3"EϚw)˦2˶xK0՞'cs<[ϟ3掋7PRU>K:= IDAT}7[zѯ=?Kn툎a!MIx1$M! 9+pɟ:obʕ,yCl9y4߽L)ߑѓ}byo]_p>fIٛb *Ubʔ)v !B!BɿS205ʯ}raE4iFZhUqJ "ʔO2@ EQҥ | իWMB!B!vAZ' nbO;tmutCisxQ~,.w=+M͂%╺8eW7~L.lΆ2)۴ ҨiY:2e:SdJV.(W(Ջqs穼ײBںJifւe\Zȳ^=YNNN6B!B!(|/Ա%Czc}Iħwct|L:l9ۉ Kp"8ϼBJ/+ 1l/{X2t0A$lo.κΐ;/t.#niTv+hDS#v>dZtUya˔>=-SzY_ 7LO})tuiSţlg$Sٳ4 E)\wvww~ݺ/Nx–b]#qGQyyyt2B! +-/MtŁ (TБ)ȑ-G}9eCѵk .C2+ۘ:Ƴ>|x-KNޔP%zX>,nV१rT83Z^JxT<ÞgyݣTvg(r: 5?ϧLɋ͜+ A燻{ݝfMYD9hw@ NB!|5Kɋ&KXMnj~ς_^_rnGl=1w3G;f#\C.gP-H:Yl|}}|-S|3z̪V Tld^ӧ]^jԕ)y:&cNLFcO:P+VӤI,iF#1ğ>m %KRNܚ4A1Xu?f(P!{XyB!( -^\7ٽ~=qqym(`mo! "73o*N:Ea{7Sێ>;\2-״DQ-֔DԪUF#Zݘ@\h(OSG̮eTB::\j#B7x8_֪a7d,ޛ4G查K4SǹG^)^eY߾9pJijߨ͕Lͷ`g>s}aK"*=U㻱W^7Yoej6ֆuPU5煅M{ՇBgFwY [f(4:: _avne~uyMfטBZSWF[Hk"A^e¼P@'iU'zя]w6Gi屌YSDmsU:~ۖ 1vg_31*1Th&ۓ0!Da$MB!x(U_}E> 4Dȳ#m;>fGvg˫KܯuhS61Pn>9?|ּ̓<{zz+2y@\1u;MFWMߊf6* ]\\UV%""F-9 &&x88i3~2yuB"5fL.5㚹'yqT+N~4KɴNen__k^5#:5F y=+5ɋޠQU!\ы>r35@^Үjf~A FG#ת_c5t݉Qsn|xA nY`$ !D#MB!x8P~}lJrb"ݪV-F84I%&tXJL櫸SsʤIz*`4S%^0b@CVW'}ҟ95EVQ*O:dUIz2X]`i.44m۲}vΟ?/]vڻ9,ŪGyvu/t :sU :9mr$<穠&7~[R#$NYEjر7iz|3;䦍M eu xo@SSDMY BF$Ba%Ks$diJd5]eMYߧ3ٕvbׇӋgV:2T4CcyT̗ҷN}Mq|Mv6˿fx$" (*dX}Y&00͛ӱcGPOUUbcc $::yx~S$Ir \8^i[wʫ] pݲlzǏ& 4OO();Ye*q0?}~c$sՐbVZV-n8f2=0O(UVzj>[3S|ˢ6!IƪB!li_U ͫR8q.0ZߜJN]xN쌓MFY71CʺR*S1@wlѨuU;wiEm-F׎4g%~hc\-8ۛWINa3Ts}343sYre`|_z&Ube m͞?;+|<;dsI?Febޮz\EꚌk֌HLIIhёr͚Y͑K~a-^O!lEXB!||Yno^|ݮUl26v=X7nh];cWJ(N?0t[\ce@rvnZ$V"aSI f؁ͮLr/.3hsB.L;'|h~?!D'fݙ曘$Ǯ~˗[˦<:ԁZ*͊f4s 'ObLHB:upkŐhfMBرc,ZEUQ4 4c%1+^v/2]Ə~~B!(Ihm?U!D{5|`{7X\fܤ*;@fͬN5'?/B=^^^Z|' !0>ydI_g!l( ^cj5iʌDa=@&MfDEEd[DΟ?kf3!B!BvNx?UU-'U}YτB!B!ånYKX/?gz^2=xf~[Z,"?f(FEsӇ={ڭԫ牛+zt  1uB!Ƽl}kƍ>ybi7~Ҡ3<u̱ m|f@ox{+|]/-`ȴs=2|aγCJtȒ)B J*燻]NӦY|O^dl傤W }_aB!B7^^^,\MSIC-1iZzy2,Oi'/Z-c5e/c2<1O[# [=l2g+ШSҟ樽W<釉IR۸#l晬x?ҳ=Lf*=Mޠc.>-K@;'}\j$;~SA?vcXW'Yz{zzhvB˸`2;I.Md`MB!BsǎcI  0_\WO^Xϟ-}sT'?Nc񁘻0׶^cs"pC)(tt.4lhd)K siкQaҚ$:Ǚ2ʢ_F0HRZh<͉*p+/s )x+,N5w19B|F,N)MW1'F(W6s~9Q_!lc lYXEUl}n=#f)n<ӯՎfh*wƠ6b5~Bmƾ։AK9Uר[>z^g\YhYNNNWq6ʕsnFv:l9ۀ K ygX*\I%Ƴe+T}QϐC=-x:C2ep-ṳm[܆8gO} ;/w'֧GѿC07~Vj*V-rs* O9toIW1oa3f206ozb"Wrl}-zՠ͇Xl sGP; 8YtԨQgg炭8ERXN؁>e_dźսllכ'c:U7hfc/iW:L&5Oc'<>eZ O^Dp .nWcF:+_ĨͻO>fɌ+Ը}W6t^7`oyFT*Jzb _zA_נAdj_l/U.#1mH4/}qoHG^Ɍc<4ue*kA|YzR1_Tٱ36{Yl;reZoy?$VB5R ą4Uz@[___}6縬B!B}9]F{)Ӗhv؂CCdݧ.مv)~,%|;?Cd^Jx{k*÷e=^&u+_ţCG5YO#Oe=.em g~gñ[8R:?EojiTԌzkqw^[;z2;1ut;v V_,bPhi<Ӛҡ 0d7/l]urlMwǡ p5n嬑M9t =[d?W޷?Pf^t#G1V6gt ٯkIہ64.FdJHk3lG6 ?G:W[s[K`3j]׃[7&;ph hT2'Mk0>R ¨9~8Wю8- *T@*iio !B!D^`Ԉ>ycٻI/%6`wW^Ѽ,ؒs!Ct+Qc_C+6>8z];Shש7ùj.m1:ș{'7uޑʩ3Xy9M_YCh"`:æap:ڸH~tU6o"򩄭0sLީkHq^ޤ\ϯ}Űc"f3a &v?fgɗ"w¡8f4McÆ 1lPzR?=nNPn n $緣|ط)ӿo Ξ˹LELu'^r]^.7-|s"{yMQPRx&D)_~ecr}WMXB!BasJ$s붆$P.jlf|)MR9C?}H̛S6{^ݰaRD)8U<(xڝa6RCww#4l8kytUvIįMO??0F7E^dͼ6IDATLyČ\(_N:NF';Y 5|ֻѢi*B!yą WP9ђOtBIJ&&r;Qc7Kԥ|z$`j>fE XYNC5sI=w58`ƾ%޺Nο6h7R/_)3.fs,Ui$}`(TE`iDLX6pVb_-qC]Ӓhl|nRb+~ߏ՜J)Z ygoEuj- 4X]`i.44m۲}vΟ?/]vH&^d*ٱn}0no=KGVJ4ieɉ={s 'ObLHB:upk` W`w-ZO!B!۱c݄,e'_6;JOޤĐ?xߦϞodO+Sot%}|=m ٗAhi޼9;va TU%66@ Θ8?AHXdW0u@zu*sF[r(5*;՜B!B؋b'/, Jξ.BZűQ3sՎzVT0YB!BQ܄Яw7.[kzkEHHH>ybM?kC%@'DMpP =6BݜbdRKpPe) !B!Dq\}uXBE5}|B:B!BgA>y;!B!B!D1#U!B!B!VB!B!BXB!B!B )fF!B!B!b?G$BXrIENDB`cecilia5-5.4.1/doc-en/source/index.rst000066400000000000000000000030211372272363700175150ustar00rootroot00000000000000.. Cecilia5 documentation master file, created by sphinx-quickstart on Thu May 23 18:54:14 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Cecilia 5.4.0 documentation ============================================= .. image:: /images/Cecilia_splash.png :scale: 75 % :align: center Welcome to the Cecilia 5.4.0 documentation! Cecilia is an audio signal processing environment. Cecilia lets you create your own GUI (grapher, sliders, toggles, popup menus) using a simple syntax. Cecilia comes with many original builtin modules for sound effects and synthesis. Cecilia uses the pyo audio engine created for the Python programming language. Pyo allows a powerful integration of the audio engine to the graphical interface. Since it's a standard python module, there is no need to use an API to communicate with the interface. The graphical user interface ---------------------------- .. toctree:: :maxdepth: 1 src/intro/description src/intro/requirements src/configuration/preferences src/configuration/menubar src/configuration/midi-osc src/interface/interface The audio processing tools available with Cecilia --------------------------------------------------- .. toctree:: :maxdepth: 2 src/modules/index The developement of custom audio processing tools -------------------------------------------------- .. toctree:: :maxdepth: 2 src/api/index Search this documentation ----------------------------- * :ref:`search` cecilia5-5.4.1/doc-en/source/src/000077500000000000000000000000001372272363700164475ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/api/000077500000000000000000000000001372272363700172205ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/api/BaseModule/000077500000000000000000000000001372272363700212405ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/api/BaseModule/index.rst000066400000000000000000000126101372272363700231010ustar00rootroot00000000000000BaseModule API =============== Here are the explanations about the processing class under every cecilia module. Declaration of the module's class ---------------------------------- Every module must contain a class named 'Module', where the audio processing will be developed. In order to work properly inside the environment, this class must inherit from the `BaseModule` class, defined inside the Cecilia source code. The BaseModule's internals create all links between the interface and the module's processing. A Cecilia module must be declared like this: .. code:: class Module(BaseModule): ''' Module's documentation ''' def __init__(self): BaseModule.__init__(self) ### Here comes the processing chain... The module file will be executed in an environment where both `BaseModule` and `pyo` are already available. No need to import anything specific to define the audio process of the module. Module's output ---------------- The last object of the processing chain (ie the one producing the output sound) must be called 'self.out'. The audio server gets the sound from this variable and sends it to the Post-Processing plugins and from there, to the soundcard. Here is an example of a typical output variable, where 'self.snd' is the dry sound and 'self.dsp' is the processed sound. 'self.drywet' is a mixing slider and 'self.env' is the overall gain from a grapher's line: .. code:: self.out = Interp(self.snd, self.dsp, self.drywet, mul=self.env) Module's documentation ----------------------- The class should provide a __doc__ string giving relevant information about the processing implemented by the module. The user can show the documentation by selecting 'Help Menu' ---> 'Show Module Info'. Here is an example: .. code:: ''' "Convolution brickwall lowpass/highpass/bandpass/bandstop filter" Description Convolution filter with a user-defined length sinc kernel. This kind of filters are very CPU expensive but can give quite good stopband attenuation. Sliders # Cutoff Frequency : Cutoff frequency, in Hz, of the filter. # Bandwidth : Bandwith, in Hz, of the filter. Used only by bandpass and pnadstop filters. # Filter Order : Number of points of the filter kernel. A longer kernel means a sharper attenuation (and a higher CPU cost). This value is only available at initialization time. Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of the filter (lowpass, highpass, bandpass, bandstop) # Balance : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Spread : Pitch variation between voices (chorus), only available at initialization time ''' Public Attributes ------------------ These are the attributes, defined in the BaseModule class, available to the user to help in the design of his custom modules. **self.sr** Cecilia's current sampling rate. **self.nchnls** Cecilia's current number of channels. **self.totalTime** Cecilia's current duration. **self.filepath** Path to the directory where is saved the current cecilia file. **self.number_of_voices** Number of voices from the cpoly widget. **self.polyphony_spread** List of transposition factors from the cpoly widget. **self.polyphony_scaling** Amplitude value according to polyphony number of voices. Public Methods --------------- These are the methods, defined in the BaseModule class, available to the user to help in the design of his custom modules. **self.addFilein(name)** Creates a SndTable object from the name of a cfilein widget. **self.addSampler(name, pitch, amp)** Creates a sampler/looper from the name of a csampler widget. **self.getSamplerDur(name)** Returns the duration of the sound used by the sampler `name`. **self.duplicate(seq, num)** Duplicates elements in a sequence according to the `num` parameter. **self.setGlobalSeed(x)** Sets the Server's global seed used by objects from the random family. Template --------- This template, saved in a file with the extension '.c5', created a basic module where a sound can be load in a sampler for reading, with optional polyphonic playback. A graph envelope modulates the amplitude of the sound over the performance duration. .. code:: class Module(BaseModule): ''' Module's documentation ''' def __init__(self): BaseModule.__init__(self) ### get the sound from a sampler/looper self.snd = self.addSampler('snd') ### mix the channels and apply the envelope from the graph self.out = Mix(self.snd, voices=self.nchnls, mul=self.env) Interface = [ csampler(name='snd'), cgraph(name='env', label='Amplitude', func=[(0,1),(1,1)], col='blue1'), cpoly() ] cecilia5-5.4.1/doc-en/source/src/api/Interface/000077500000000000000000000000001372272363700211205ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/api/Interface/cbutton.rst000066400000000000000000000016021372272363700233270ustar00rootroot00000000000000cbutton : creates a button that can be used as an event trigger =============================================================== Initline --------- .. code:: cbutton(name='button', label='Trigger', col='red', help='') Description ------------ A button has no state, it only sends a trigger when it is clicked. When the button is clicked, a function is called with the current state of the mouse (down or up) as argument. If `name` is set to 'foo', the function should be defined like this : .. code:: def foo(self, value): value is True on mouse pressed and False on mouse released. Parameters ----------- **name**: str Name of the widget. Used to defined the function. **label**: str Label shown in the interface. **col**: str Color of the widget. **help**: str Help string shown in the button tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/cfilein.rst000066400000000000000000000026301372272363700232640ustar00rootroot00000000000000cfilein : creates a popup menu to load a soundfile in a table ============================================================= Initline --------- .. code:: cfilein(name='filein', label='Audio', help='') Description ------------ This interactive menu allows the user to import a soundfile into the processing module. When the user chooses a sound using the interface, Cecilia will scan the whole folder for soundfiles. A submenu containing all soundfiles present in the folder will allow a quicker access to them later on. More than one cfilein can be defined in a module. They will appear under the input label in the left side panel of the main window, in the order defined. In the processing class, use the BaseModule's method `addFilein` to retrieve the SndTable filled with the selected sound. .. code:: BaseModule.addFilein(name) For a cfilein created with name='mysound', the table is retrieved using a call like this one: .. code:: self.table = self.addFilein('mysound') Parameters ----------- **name**: str A string passed to the parameter `name` of the BaseModule.addFilein method. This method returns a SndTable object containing Cecilia's number of channels filled with the selected sound in the interface. **label**: str Label shown in the interface. **help**: str Help string shown in the widget's popup tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/cgen.rst000066400000000000000000000036751372272363700226010ustar00rootroot00000000000000cgen : creates a list entry useful to generate list of arbitrary values ======================================================================= Initline --------- .. code:: cgen(name='gen', label='Wave shape', init=[1,0,.3,0,.2,0,.143,0,.111], rate='k', popup=None, col='red', help='') Description ------------ Widget that can be used to create a list of floating-point values. A left click on the widget will open a floating window to enter the desired values. Values can be separated by commas or by spaces. If `rate` argument is set to 'i', a built-in reserved variable is created at initialization time. The variable name is constructed like this : .. code:: self.widget_name + '_value' for retrieving a list of floats. If `name` is set to 'foo', the variable name will be: .. code:: self.foo_value (this variable is a list of floats) If `rate` argument is set to 'k', a module method using one argument must be defined with the name `name`. If `name` is set to 'foo', the function should be defined like this : .. code:: def foo(self, value): value -> list of strings Parameters ----------- **name**: str Name of the widget. Used to defined the function or the reserved variable. **label**: str Label shown in the interface. **init**: int An array of number, separated with commas, with which to initialize the widget. **rate**: str {'k', 'i'} Indicates if the widget is handled at initialization time only ('i') with a reserved variable or with a function ('k') that can be called at any time during playback. **popup**: tuple (str, int) -> (popup's name, index) If a tuple is specified, and cgen is modified, the popup will be automatically set to the given index. **col**: str Color of the widget. **help**: str Help string shown in the widget's tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/cgraph.rst000066400000000000000000000037401372272363700231220ustar00rootroot00000000000000cgraph : creates a graph only automated parameter or a shapeable envelope ========================================================================= Initline --------- .. code:: cgraph(name='graph', label='Envelope', min=0.0, max=1.0, rel='lin', table=False, size=8192, unit='x', curved=False, func=[(0, 0.), (.01, 1), (.99, 1), (1, 0.)], col='red') Description ------------ A graph line represents the evolution of a variable during a Cecilia performance. The value of the graph is passed to the module with a variable named `self.name`. The 'func' argument defines an initial break-point line shaped with time/value pairs (floats) as a list. Time values must be defined from 0 to 1 and are multiplied by the total_time of the process. When True, the 'table' argument writes the graph line in a PyoTableObject named with the variable `self.name`. The graph can then be used for any purpose in the module by recalling its variable. The `col` argument defines the color of the graph line using a color value. Parameters ----------- **name**: str Name of the grapher line. **label**: str Label shown in the grapher popup. **min**: float Minimum value for the Y axis. **max**: float Maximum value for the Y axis. **rel**: str {'lin', 'log'} Y axis scaling. **table**: boolean If True, a PyoTableObject will be created instead of a control variable. **size**: int Size, in samples, of the PyoTableObject. **unit**: str Unit symbol shown in the interface. **curved**: boolean If True, a cosinus segments will be drawn between points. The curved mode can be switched by double-click on the curve in the grapher. Defaults to Flase **func**: list of tuples Initial graph line in break-points (serie of time/value points). Times must be in increasing order between 0 and 1. **col**: str Color of the widget. cecilia5-5.4.1/doc-en/source/src/api/Interface/colours.rst000066400000000000000000000006551372272363700233460ustar00rootroot00000000000000Colours ======== Five colours, with four shades each, are available to build the interface. The colour should be given, as a string, to the `col` argument of a widget function. .. code:: red1 blue1 green1 purple1 orange1 red2 blue2 green2 purple2 orange2 red3 blue3 green3 purple3 orange3 red4 blue4 green4 purple4 orange4 cecilia5-5.4.1/doc-en/source/src/api/Interface/cpoly.rst000066400000000000000000000036511372272363700230050ustar00rootroot00000000000000cpoly : creates two popup menus used as polyphony manager ========================================================= Initline --------- .. code:: cpoly(name='poly', label='Polyphony', min=1, max=10, init=1, help='') Description ------------ cpoly is a widget conceived to help manage the voice polyphony of a module. cpoly comes with a popup menu that allows the user to choose how many instances (voices) of a process will be simultaneously playing. It also provides another popup to choose the type of polyphony (phasing, chorus, out-of-tune or one of the provided chords). cpoly has two values that are passed to the processing module: the number of voices and the voice spread. The number of voices can be collected using `self.number_of_voices`. `self.polyphony_spread` gives access to the transposition factors defined by the type of polyphony. If a csampler is used, you don't need to take care of polyphony, it's automatically handled inside the csampler. Without a csampler, user can retrieve polyphony popups values with these builtin reserved variables : **self.number_of_voices**: int Number of layers of polyphony **self.polyphony_spread**: list of floats Transposition factors as a list of floats **self.polyphony_scaling**: float An amplitude factor based on the number of voices Notes ------- The cpoly interface object and its associated variables can be used in the dsp module any way one sees fit. No more than one `cpoly` can be declared in a module. Parameters ----------- **name**: str Name of the widget. **label**: str Label shown in the interface. **min**: int Minimum value for the number of layers slider. **max**: int Maximum value for the number of layers slider. **init**: int Initial value for the number of layers slider. **help**: str Help string shown in the cpoly tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/cpopup.rst000066400000000000000000000035221372272363700231620ustar00rootroot00000000000000cpopup : creates a popup menu offering a limited set of choices =============================================================== Initline --------- .. code:: cpopup(name='popup', label='Chooser', value=['1', '2', '3', '4'], init='1', rate='k', col='red', help='') Description ------------ A popup menu offers a limited set choices that are available to modify the state of the current module. If `rate` argument is set to 'i', two built-in reserved variables are created at initialization time. The variables' names are constructed like this : .. code:: self.widget_name + '_index' for the selected position in the popup. self.widget_name + '_value' for the selected string in the popup. If `name` is set to 'foo', the variables names will be: .. code:: self.foo_index (this variable is an integer) self.foo_value (this variable is a string) If `rate` argument is set to 'k', a module method using two arguments must be defined with the name `name`. If `name` is set to 'foo', the function should be defined like this : .. code:: def foo(self, index, value): index -> int value -> str Parameters ----------- **name**: str Name of the widget. Used to defined the function or the reserved variables. **label**: str Label shown in the interface. **value**: list of strings An array of strings with which to initialize the popup. **init**: int Initial state of the popup. **rate**: str {'k', 'i'} Indicates if the popup is handled at initialization time only ('i') with reserved variables or with a function ('k') that can be called at any time during playback. **col**: str Color of the widget. **help**: str Help string shown in the popup tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/crange.rst000066400000000000000000000061071372272363700231150ustar00rootroot00000000000000crange : two-sided slider, with its own graph lines, for time-varying controls ============================================================================== Initline --------- .. code:: crange(name='range', label='Pitch', min=20.0, max=20000.0, init=[500.0, 2000.0], rel='log', res='float', gliss=0.025, unit='x', up=False, func=None, midictl=None, col='red', help='') Description ------------ This function creates a two-sided slider used to control a minimum and a maximum range at the sime time. When created, the range slider is stacked in the slider pane of the main Cecilia window in the order it is defined. The values of the range slider are passed to the module with a variable named `self.name`. The range minimum is collected using `self.name[0]` and the range maximum is collected using `self.name[1]`. The `up` argument passes the values of the range on mouse up if set to True or continuously if set to False. The `gliss` argument determines the duration of the portamento (in sec) applied on a new value. The resolution of the range slider can be set to 'int' or 'float' using the `res` argument. Slider color can be set using the `col` argument and a color value. However, sliders with `up` set to True are greyed out and the `col` argument is ignored. Every time a range slider is defined, two graph lines are automatically defined for the grapher in the Cecilia interface. One is linked to the minimum value of the range, the other one to the maximum value of the range. The recording and playback of an automated slider is linked to its graph line. Notes ------- In order to quickly select the minimum value (and graph line), the user can click on the left side of the crange label, and on the right side of the label to select the maximum value (and graph line). Parameters ----------- **name**: str Name of the range slider. **label**: str Label shown in the crange label and the grapher popup. **min**: float Minimum value of the range slider. **max**: float Maximum value of the range slider. **init**: list of float Range slider minimum and maximum initial values. **rel**: str {'lin', 'log'} Range slider scaling. Defaults to 'lin'. **res**: str {'int', 'float'} Range slider resolution. Defaults to 'float' **gliss**: float Portamento between values in seconds. Defaults to 0.025. **unit**: str Unit symbol shown in the interface. **up**: boolean Value passed on mouse up if True. Defaults to False. **func**: list of list of tuples Initial automation in break-points format (serie of time/value points). Times must be in increasing order between 0 and 1. The list must contain two lists of points, one for the minimum value and one for the maximum value. **midictl**: list of int Automatically map two midi controllers to this range slider. Defaults to None. **col**: str Color of the widget. **help**: str Help string shown in the crange tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/csampler.rst000066400000000000000000000043151372272363700234630ustar00rootroot00000000000000csampler : creates a popup menu to load a soundfile in a sampler ================================================================ Initline --------- .. code:: csampler(name='sampler', label='Audio', help='') Description ------------ This menu allows the user to choose a soundfile for processing in the module. More than one csampler can be defined in a module. They will appear under the input label in the left side panel of the main window, in the order they have been defined. When the user chooses a sound using the interface, Cecilia will scan the whole folder for soundfiles. A submenu containing all soundfiles present in the folder will allow a quicker access to them later on. Loop points, pitch and amplitude parameters of the loaded soundfile can be controlled by the csampler window that drops when clicking the triangle just besides the name of the sound. A sampler returns an audio variable containing Cecilia's number of output channels regardless of the number of channels in the soundfile. A distribution algorithm is used to assign X number of channels to Y number of outputs. In the processing class, use the BaseModule's method `addSampler` to retrieve the audio variable containing all channels of the looped sound. .. code:: BaseModule.addSampler(name, pitch, amp) For a csampler created with name='mysound', the audio variable is retrieved using a call like this one: .. code:: self.snd = self.addSampler('mysound') Audio LFOs on pitch and amplitude of the looped sound can be passed directly to the addSampler method: .. code:: self.pitlf = Sine(freq=.1, mul=.25, add=1) self.amplf = Sine(freq=.15, mul=.5, add=.5) self.snd = self.addSampler('mysound', self.pitlf, self.amplf) Parameters ----------- **name**: str A string passed to the parameter `name` of the BaseModule.addSampler method. This method returns a Mix object containing Cecilia's number of channels as audio streams from a Looper object controlled with the sampler window of the interface. **label**: str Label shown in the interface. **help**: str Help string shown in the sampler popup's tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/cslider.rst000066400000000000000000000055631372272363700233100ustar00rootroot00000000000000cslider : creates a slider, and its own graph line, for time-varying controls ============================================================================= Initline --------- .. code:: cslider(name='slider', label='Pitch', min=20.0, max=20000.0, init=1000.0, rel='lin', res='float', gliss=0.025, unit='x', up=False, func=None, midictl=None, half=False, col='red', help='') Description ------------ When created, the slider is stacked in the slider pane of the main Cecilia window in the order it is defined. The value of the slider is passed to the module with a variable named `self.name`. The `up` argument passes the value of the slider on mouse up if set to True or continuously if set to False. The `gliss` argument determines the duration of the portamento (in seconds) applied between values. The portamento is automatically set to 0 if `up` is True. The resolution of the slider can be set to int or float using the `res` argument. Slider color can be set using the `col` argument and a color value. However, sliders with `up` set to True are greyed out and the `col` argument is ignored. If `up` is set to True, the cslider will not create an audio rate signal, but will call a method named `widget_name` + '_up'. This method must be defined in the class `Module`. For a cslider with the name 'grains', the method should be declared like this: .. code:: def grains_up(self, value): Every time a slider is defined with `up` set to False, a corresponding graph line is automatically defined for the grapher in the Cecilia interface. The recording and playback of an automated slider is linked to its graph line. Parameters ----------- **name**: str Name of the slider. **label**: str Label shown in the slider label and the grapher popup. **min**: float Minimum value of the slider. **max**: float Maximum value of the slider. **init**: float Slider's initial value. **rel**: str {'lin', 'log'} Slider scaling. Defaults to 'lin'. **res**: str {'int', 'float'} Slider resolution. Defaults to 'float' **gliss**: float Portamento between values in seconds. Defaults to 0.025. **unit**: str Unit symbol shown in the interface. **up**: boolean Value passed on mouse up if True. Defaults to False. **func**: list of tuples Initial automation in break-points format (serie of time/value points). Times must be in increasing order between 0 and 1. **midictl**: int Automatically map a midi controller to this slider. Defaults to None. **half**: boolean Determines if the slider is full-width or half-width. Set to True to get half-width slider. Defaults to False. **col**: str Color of the widget. **help**: str Help string shown in the cslider tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/csplitter.rst000066400000000000000000000047351372272363700236740ustar00rootroot00000000000000csplitter : creates a multi-knobs slider used to split the spectrum in sub-regions ================================================================================== Initline --------- .. code:: csplitter(name='splitter', label='Pitch', min=20.0, max=20000.0, init=[500.0, 2000.0, 5000.0], rel='log', res='float', gliss=0.025, unit='x', up=False, num_knobs=3, col='red', help='') Description ------------ When created, the splitter is stacked in the slider pane of the main Cecilia window in the order it is defined. The values of the splitter slider are passed to the module with a variable named `self.name`. The knob values are collected using `self.name[0]` to `self.name[num-knobs-1]`. The `up` argument passes the values of the splitter on mouse up if set to True or continuously if set to False. The `gliss` argument determines the duration of the portamento (in seconds) applied between values. The resolution of the splitter slider can be set to int or float using the `res` argument. The slider color can be set using the `col` argument and a color value. However, sliders with `up` set to True are greyed out and the `col` argument is ignored. The csplitter is designed to be used with the FourBand() object in order to allow multi-band processing. Although the FourBand() parameters can be changed at audio rate, it is not recommended. This filter is CPU intensive and can have erratic behavior when boundaries are changed too quickly. Parameters ----------- **name**: str Name of the splitter slider. **label**: str Label shown in the csplitter label. **min**: float Minimum value of the splitter slider. **max**: float Maximum value of the splitter slider. **init**: list of float Splitter knobs initial values. List must be of length `num_knobs`. Defaults to [500.0, 2000.0, 5000.0]. **rel**: str {'lin', 'log'} Splitter slider scaling. Defaults to 'lin'. **res**: str {'int', 'float'} Splitter slider resolution. Defaults to 'float' **gliss**: float Portamento between values in seconds. Defaults to 0.025. **unit**: str Unit symbol shown in the interface. **up**: boolean Value passed on mouse up if True. Defaults to False. **num_knobs**: int Number of junction knobs. Defaults to 3. **col**: str Color of the widget. **help**: str Help string shown in the csplitter tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/ctoggle.rst000066400000000000000000000031651372272363700233030ustar00rootroot00000000000000ctoggle : creates a two-states button ===================================== Initline --------- .. code:: ctoggle(name='toggle', label='Start/Stop', init=True, rate='k', stack=False, col='red', help='') Description ------------ A toggle button is a two-states switch that can be used to start and stop processes. If `rate` argument is set to 'i', a built-in reserved variable is created at initialization time. The variable's name is constructed like this : .. code:: self.widget_name + '_value' If `name` is set to 'foo', the variable's name will be: .. code:: self.foo_value If `rate` argument is set to 'k', a module method using one argument must be defined with the name `name`. If `name` is set to 'foo', the function should be defined like this : .. code:: def foo(self, value): value is an integer (0 or 1). Parameters ----------- **name**: str Name of the widget used to defined the function or the reserved variable. **label**: str Label shown in the interface. **init**: int Initial state of the toggle. **rate**: str {'k', 'i'} Indicates if the toggle is handled at initialization time only ('i') with a reserved variable or with a function ('k') that can be called at any time during playback. **stack**: boolean If True, the toggle will be added on the same row as the last toogle with stack=True and a label not empty. Defaults to False. **col**: str Color of the widget. **help**: str Help string shown in the toggle tooltip. cecilia5-5.4.1/doc-en/source/src/api/Interface/example1.rst000066400000000000000000000045241372272363700233730ustar00rootroot00000000000000Example 1 ============================================== .. code:: # This example shows how to use the sampler to loop any soundfile from the disk. # A state-variable filter is then applied on the looped sound. class Module(BaseModule): """ "State Variable Filter" Description This module implements lowpass, bandpass and highpass filters in parallel and allow the user to interpolate on an axis lp -> bp -> hp. Sliders # Cutoff/Center Freq : Cutoff frequency for lp and hp (center freq for bp) # Filter Q : Q factor (inverse of bandwidth) of the filter # Type (lp->bp->hp) : Interpolating factor between filters # Dry / Wet : Mix between the original and the filtered signals Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Chords : Pitch interval between voices (chords), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.dsp = SVF(self.snd, self.freq, self.q, self.type) self.out = Interp(self.snd, self.dsp, self.drywet, mul=self.env) Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="freq", label="Cutoff/Center Freq", min=20, max=20000, init=1000, rel="log", unit="Hz", col="green1"), cslider(name="q", label="Filter Q", min=0.5, max=25, init=1, rel="log", unit="x", col="green2"), cslider(name="type", label="Type (lp->bp->hp)", min=0, max=1, init=0.5, rel="lin", unit="x", col="green3"), cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"), cpoly() ] cecilia5-5.4.1/doc-en/source/src/api/Interface/example2.rst000066400000000000000000000062721372272363700233760ustar00rootroot00000000000000Example 2 ============================================== .. code:: # This example shows how to load a sound in a table (RAM) in order to apply # non-streaming effects. Here a frequency self-modulated reader is used to # create new harmonics, in a way similar to waveshaping distortion. class Module(BaseModule): """ "Self-modulated frequency sound looper" Description This module loads a sound in a table and apply a frequency self-modulated playback of the content. A Frequency self-modulation occurs when the output sound of the playback is used to modulate the reading pointer speed. That produces new harmonics in a way similar to waveshaping distortion. Sliders # Transposition : Transposition, in cents, of the input sound # Feedback : Amount of self-modulation in sound playback # Filter Frequency : Frequency, in Hertz, of the filter # Filter Q : Q of the filter (inverse of the bandwidth) Graph Only # Overall Amplitude : The amplitude curve applied on the total duration of the performance Popups & Toggles # Filter Type : Type of the filter # Polyphony Voices : Number of voices played simultaneously (polyphony), only available at initialization time # Polyphony Chords : Pitch interval between voices (chords), only available at initialization time """ def __init__(self): BaseModule.__init__(self) self.snd = self.addFilein("snd") self.trfactor = CentsToTranspo(self.transpo, mul=self.polyphony_spread) self.freq = Sig(self.trfactor, mul=self.snd.getRate()) self.dsp = OscLoop(self.snd, self.freq, self.feed*0.0002, mul=self.polyphony_scaling * 0.5) self.mix = self.dsp.mix(self.nchnls) self.out = Biquad(self.mix, freq=self.filt_f, q=self.filt_q, type=self.filt_t_index, mul=self.env) def filt_t(self, index, value): self.out.type = index Interface = [ cfilein(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"), cslider(name="transpo", label="Transposition", min=-4800, max=4800, init=0, unit="cnts", col="red1"), cslider(name="feed", label="Feedback", min=0, max=1, init=0.25, unit="x", col="purple1"), cslider(name="filt_f", label="Filter Frequency", min=20, max=18000, init=10000, rel="log", unit="Hz", col="green1"), cslider(name="filt_q", label="Filter Q", min=0.5, max=25, init=1, rel="log", unit="x", col="green2"), cpopup(name="filt_t", label="Filter Type", init="Lowpass", value=["Lowpass", "Highpass", "Bandpass", "Bandreject"], col="green1"), cpoly() ] cecilia5-5.4.1/doc-en/source/src/api/Interface/index.rst000066400000000000000000000004541372272363700227640ustar00rootroot00000000000000Interface API ============== The next functions describe every available widgets for building a Cecilia5 interface. .. toctree:: :maxdepth: 1 cfilein csampler cpoly cgraph cslider crange csplitter ctoggle cpopup cbutton cgen colours example1 example2 cecilia5-5.4.1/doc-en/source/src/api/index.rst000066400000000000000000000021571372272363700210660ustar00rootroot00000000000000Cecilia API Documentation ============================= What is a Cecilia module ------------------------- A Cecilia module is a python file (with the extension 'C5', associated to the application) containing a class named `Module`, within which the audio processing chain is developed, and a list called `Interface`, telling the software what are the graphical controls necessary for the proper operation of the module. the file can then be loaded by the application to apply the process on different audio signals, whether coming from sound files or from the microphone input. Processes used to manipulate the audio signal must be written with the Python's dedicated signal processing module 'pyo'. API Documentation Structure ---------------------------- This API is divided into two parts: firstly, there is the description of the parent class, named `BaseModule`, from which every module must inherit. This class implements a lot of features that ease the creation of a dsp chain. Then, the various available GUI elements (widgets) are presented. .. toctree:: :maxdepth: 2 BaseModule/index Interface/index cecilia5-5.4.1/doc-en/source/src/configuration/000077500000000000000000000000001372272363700213165ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/configuration/menubar.rst000066400000000000000000000120571372272363700235060ustar00rootroot00000000000000Menu Options ============== This is a short description of the menus that the user will find in the upper part of the screen. File Menu ----------------- The *File* menu gives access to the following commands: - **Open**: Opens a Cecilia file (with the extension .c5) saved by the user. - **Open Random**: Chooses a random Cecilia module and opens it. - **Open Module**: In this menu, the user can choose a Cecilia module. The modules fall in eight categories: #. **Dynamics**: Modules related to waveshaping and amplitude manipulations. #. **Filters**: Filtering and subtractive synthesis modules. #. **Multiband**: Various processing applied independently to four spectral regions. #. **Pitch**: Modules related to playback speed and pitch manipulations. #. **Resonators&Verbs**: Artificial spaces generation modules. #. **Spectral**: Spectral streaming processing modules. #. **Synthesis**: Additive synthesis and particle generators. #. **Time**: Granulation based time-stretching and delay related modules. - **Open Recent**: Opens a Cecilia file which has been recently modified and saved by the user. - **Save** and **Save as**: Saves the module on which the user is currently working. The saved file is plain text file with the ".c5" extension. - **Open Module as Text**: Opens a text file that contains the source code of the module, for more information on it or if the user wishes to modify it. If no text editor has been selected in the Preferences yet, a dialog window will appear to let the user choose one. - **Reload module**: Reloads the module from the source code. The user should execute this command if the module source code has been modified. - **Preferences...**: Opens the preferences window (Useful to comfigure the application's behaviour for a specific operating system). - **Quit**: Properly quit the application. Edit Menu ----------------- The *Edit* menu gives access to the following commnds: - **Undo**: Revert the grapher to the previous state. - **Redo**: Redo the last action undo'ed. - **Copy**: Copy to the clipboard the list of points of the grapher's current line. - **Paste**: Set the grapher's current line to the clipboard content (assumed to be a valid list of points). - **Select All Points**: Select all points in the grapher's current line. - **Remember Input Sound**: Check this option if you wish that Cecilia5 remembers the input sound you chose for each new module opening. Action Menu ----------------- The *Action* menu gives access to the following commnds: - **Play/Stop**: Start and stop the playback of the process. - **Bounce to Disk**: Computes the process as fast as possible and writes the result in a soundfile. - **Batch Processing on Preset Sequence**: Applies all saved presets of the module to the current input soundfile. - **Batch Processing on Sound Folder**: Applies the module's current settings to all soundfiles present in the folder that contains the current input soundfile. The two previous commands processes more than one sound file at a time. Before executing one of these commands, Cecilia will ask the user to enter a suffix to identify the new created soundfiles. A folder with the name of the suffix given by the user will be created in the same directory as the current input soundfile. If the module doesn't have an input soundfile (a synthesis module), the desktop will be used as the root directory. - **Use Sound Duration on Folder Batch Processing**: When processing a folder of sounds in batch mode, if this option is checked, the total duration will be automatically adjusted to the duration of the processed sound. - **Show Spectrum**: Open a window showing the spectral component of the sound that is heard. The **Use MIDI** menu item indicates if Cecilia has already found a MIDI device. Help Menu ----------------- The *Help* menu gives access to the following commnds: - **Show module info**: Opens the module documentation window at the page of the module that is currently used. - **Show API Documentation**: Opens the documentation related to Cecilia's Application Programming Interface (API). This documentation contains all classes and functions declarations available to design new custom module. - **Show Grapher Help**: Shows the list of mouse bindings applied to the grapher. List of Shortcuts ------------------- - **Ctrl+O**: Open a Cecilia file - **Shift+Ctrl+O**: Open a Cecilia file randomly - **Ctrl+S**: Save the current file - **Shift+Ctrl+S**: Save the current file as... - **Ctrl+E**: Open the text file that contains the source code of the module - **Ctrl+R**: Reload module with all saved modifications in the source code - **Ctrl+,**: Open the preferences window - **Ctrl+Q**: Quit the application - **Ctrl+Z**: Undo (grapher only) - **Shift+Ctrl+Z**: Redo (grapher only) - **Ctrl+C**: Copy (grapher only) - **Ctrl+V**: Paste (grapher only) - **Ctrl+A**: Paste (grapher only) - **Ctrl+.**: Play/Stop - **Ctrl+B**: Bounce to disk - **Shift+Ctrl+E**: Eh Oh Mario! - **Ctrl+I**: Open module documentation - **Ctrl+D**: Open API Documentation - **Ctrl+G**: Open Grapher Help cecilia5-5.4.1/doc-en/source/src/configuration/midi-osc.rst000066400000000000000000000061261372272363700235610ustar00rootroot00000000000000MIDI - OSC Control =================== .. _midiosc: It is possible to use a MIDI controller or an *Open Sound Control* device to have a finer control on sliders and knobs related to the different parameters in Cecilia. MIDI ------- To use a MIDI controller, please connect it to your system before and be sure that it has been detected by your computer before launching Cecilia. If you have multiple MIDI devices connected, use the preferences window to select the one you want to use with Cecilia (or the "all" option if you want to use many of them). If the option "Automatic Midi Bindings" is checked in the MIDI Preferences, Cecilia will automatically make two connections between your device and the interface. First, The controller 7 of the selected device will be assigned to the gain control (in decibels) of the output sound file (see the corresponding slider in the "Output" section). Second, the MIDI keyboard will be connected to the transposition slider of any sampler present in the "Input" section. However, most parameters will have to be assigned to a controller with the MIDI learn function. The "Range Slider" (slider with two bounds) parameters will have to be assigned to two different controllers, for the minimum and maximum values. To link a parameter to a MIDI controller with the MIDI learn function, **Right-Click** on the parameter label (or on the knob in Post-Processing section) you want to control and move the knob or slider of the MIDI controller to enable the connection. Then, the controller number should be written in the slider of Cecilia's graphical interface. To disable the connection, hold **Shift** and **Right-Click** on the parameter label. Sliders in the sampler window can also be assigned to MIDI controllers. .. image:: /images/midiLearn.png :align: center OSC ------- It is also possible to control the parameters with the Open Sound Control (OSC) protocol. To enable an OSC connection, **Double-Click** on the parameter label (knobs in Post-Processing section can't be assigned to OSC messages but sliders in the sampler window can) you want to control, enter the destination port and address in the window that will appear and click on "Apply": .. image:: /images/OpenSoundControl.png :align: center :scale: 90 In the first box, you should enter the *port:address* pair where Cecilia will read values coming from the controller. Optionally, the second box can be used to set a triplet *host:port:address* identifying the controller IP and the concerned widget. If not empty, Cecilia will send its widget value to initialize the controller at the beginning of the playback. To assign OSC controller to a *Range Slider*, you must **Double-Click** one time on the left part of the parameter label to create a connection with the minimum value and **Double-Click** another time on the right part of the label to create the connection with the maximum value. Sliders in the sampler window can also be assigned to OSC messages. Please be aware that activating an OSC connection will automatically disable the previons MIDI connection related to the chosen parameter. cecilia5-5.4.1/doc-en/source/src/configuration/preferences.rst000066400000000000000000000075151372272363700243610ustar00rootroot00000000000000Setting Up the Environment ================================ Preferences panel -------------------- The Preferences panel (accessible with the shortcut "Ctrl+," or in the *File* menu (*Cecilia5* menu under OSX)) allows the user to configure the application's behaviour for a specific operating system. The window is separated into five sections. Path -------- In the *Path* tab, the user can choose a sound file player (or audio sequencer - see "Soundfile Player"), a sound file editor (see "Soundfile editor") and a text editor (see "Text Editor") to be used with Cecilia5. To choose an application, enter the path of the application in the appropriate space or click on "set" button. A dialog window will then appear for choosing the application. The user can also enter a path (or multiple path separated by semicolon (;)) in the "Preferred paths" box to save custom modules (.c5 files) in specific folders. Cecilia5 will then scan these folders and add new categories/modules in the *Files* -> *Modules* menu. .. image:: /images/Onglet_dossier.png :align: center :scale: 100 Audio -------- The *Speaker* tab offers different options related to the audio parameters of Cecilia5. The user can choose an audio driver (see "Audio Driver") and the input and output devices (see "Input Device" and "Output Device"). The check box in front of the input device menu must be checked in order to get live input signal in Cecilia5. The popup menus "Sample Precision" and "Buffer Size" contains different values to set the sample precision in bits (32 or 64) and the buffer size in samples (64, 128, 256, 512, 1024 or 2048). The user can also choose the default number of audio channels (up to 36 - see "Default # of channels") and the sample rate (22 050, 44 100, 48 000, 88 200 or 96 000 Hertz - see "Sample Rate"). The last two popups allows the user to give an offset to the soundcard input and output channels. In stereo mode, with "First Physical Output" set to 8, the signal will be sent to outputs 8 and 9 (beginning at 0). .. image:: /images/Onglet_haut-parleur.png :align: center :scale: 100 MIDI ------- In the *MIDI* tab, the user can choose a MIDI driver (PortMidi is the only available driver at this time) and a MIDI controller for input (see "Input Device") **Warning**, the MIDI controller should be already connected and detected by the computer to be detected by Cecilia5. If the "Automatic Midi Bindings" box is checked, the controller 7 of the selected MIDI device will automatically be assigned to the Gain slider (in the "In/Out" section of the control panel) and the MIDI keyboard will be binded to the sampler's transposition slider. .. image:: /images/Onglet_MIDI.png :align: center :scale: 100 Export ---------- In the *Export* tab, the user can set the default file format (WAV, AIFF, FLAC, OGG, SD2, AU, CAF) and the bit depth (16, 24 or 32 bits integer, or 32 bits float) of the output sound files that will be exported to hard drive. .. image:: /images/Onglet_export.png :align: center :scale: 100 Cecilia -------- The *Cecilia5* tab provides different settings: - The default duration (10, 30, 60, 120, 300, 600, 1200, 2400 or 3600 seconds - see "Total time default (sec)" of the output sound file and its default fadein/fadeout duration (0, 0.001, 0.002, 0.003, 0.004, 0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.05, 0.075, 0.1, 0.2, 0.3, 0.4 or 0.5 seconds - see "Global fadein/fadeout (sec)"). - Check the "Use tooltips" box to see the tooltip information windows appear in the graphical interface when the mouse is over a particular widget. - Check the "Use grapher texture" to obtain a textured background on the graph. - Check the "Verbose" box to obtain more information when you work with Cecilia5 from the terminal, which can be useful for debugging. .. image:: /images/Onglet_Cecilia5.png :align: center :scale: 100 cecilia5-5.4.1/doc-en/source/src/interface/000077500000000000000000000000001372272363700204075ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/interface/interface.rst000066400000000000000000000453671372272363700231200ustar00rootroot00000000000000Cecilia Interface ====================== In Cecilia, all built-in modules come with a graphical interface, which is divided in different sections, common to all treatment and synthesis modules of the software: the transports panel, the In/Out and Post-Processing tabs, the Presets section, the grapher, toggles&popups and the sliders that control the parameters of the module. All these sections will be described in this chapter. .. image:: /images/Interface-graphique.png :align: center :scale: 80 Transport panel ----------------- The transport section contains a window that indicates the current time of the playback and two buttons: **Play/Stop** and **Record**. .. image:: /images/Transport.png :align: center - **Play/stop** button: Press to launch playback of the output sound file. Click again to stop. - **Record** button: Press to record the output sound to a sound file in realtime. You will hear the playback but priority is given to disk writing. A "Save audio file as ..." dialog window will appear if the output file name is not already defined. If multiple recordings with the same output file name are done, Cecilia will append "_xxx" to file names, where "xxx" is a three digits incremented number. By default, all sound files will be recorded in AIFF format (soundfile format and resolution can be changed in the preferences panel) and will be named by the name of the module (with the extension .aif). Input - Output ---------------- The In/Out tab, which is situated below the transport panel, features different control options related to the input/output sound files. Input ******** .. image:: /images/Input.png :align: center This section is only provided with the treatment modules. To import an audio file from hard drive, click on the popup menu, below the label "Audio", and select your file from the standard dialog. Accepted file formats are WAV, AIFF, FLAC, OGG, SD2, AU or CAF. All audio files located in the same folder as the chosen file will be loaded in the popup menu. To open the popup menu and select another pre-loaded soundfile, click on the triangle at the right of the menu. **Hint** : A right-click on the popup menu will open a window with the last opened sound files, for a quick access. **Input modes** The icon just at the right of the popup menu lets the user to switch the input mode of Cecilia. Four modes are available: .. image:: /images/inputMode1.png :align: left mode 1 : classic mode, load a soundfile in a sampler or a table. .. image:: /images/inputMode2.png :align: left mode 2 : uses the live input sound (eg. inputs from soundcard) to feed the module's processing (not available with modules using a table as input (ex. Granulator)). .. image:: /images/inputMode3.png :align: left mode 3 : uses the live input sound (eg. inputs from soundcard) to fill the sampler buffer (instead of loading a soundfile). .. image:: /images/inputMode4.png :align: left mode 4 : uses a double buffer to continuously fill the sampler with new samples from the live input sound (not available with modules using a table as input). **Input tools** In the Input section, the toolbox at the far right presents three icons that are shortcuts to some features of Cecilia: .. image:: /images/Icones-In.png Click on the *loudspeaker* to open the imported sound file with your favorite sound player. If no application has been selected in the Preferences yet, a dialog window will appear. Click on the *scissors* to edit the sound file in an editor application. If no application has been selected in the Preferences yet, a dialog window will appear. Click on the *triangle* to open the sampler frame dialog window for more options on the source sound file. **Sampler Controls** .. image:: /images/SourceSoundControls.png :align: center In this window, as in the main Input section, click on the *loudspeaker* to play the source sound file or on the *scissors* to edit the source sound file with an external application. Click on the icon *clock* to set the duration of the output sound to the source sound duration. .. image:: /images/Icones-SSC.png Controls over the sampler behaviour are: - The "Audio Offset" slider sets the offset time into the source sound (start position in the source sound file). In the "Loop" menu, there are four options available: *Off* (no loop), *Forward* (forward loop), *Backward* (backward loop) and *Back & Forth* (forward and backward loop in alternance). - If the "Start from loop" box is checked, Cecilia will start reading the sound file from the loop point. - The "Xfade" widget controls the shape of the crossface between loops. Choices are: *Linear*, *Equal Power* and *Sine/Cosine*. - The "Loop In" slider sets the start position of the loop in the source soundfile. - The "Loop Time" slider sets the duration (in seconds) of the loop. - The "Loop X" slider sets the duration of the crossfade between two loops (percentage of the total loop time). - The "Gain" slider sets the intensity (in decibels) of the sound source. - Move the "Transpo" slider to transpose (direct transposition) the looped sound. The duration of the loop is affected by the transposition, as while reading a tape at different speeds. **Automations** All settings can be controlled through automations. To record an automation, click on the red circle on the left side of the slider and press play (on the transport bar) to start the playback. All slider variations will then be recorded and exposed in the grapher at the end of the playback. Afterwards, you can modify the automation curve in the graphic (see the corresponding following section). Then, click on the green triangle to enable automation while playing the sound file. **Bindings** As with module's sliders below the grapher, sampler sliders can be controlled with MIDI controller or OSC device. Start the MIDI learn algorithm with a **Right-Click** on the slider label or open the OSC assignation window with a **Double-Click** on the slider label. See *MIDI - OSC Control* for more details. Output ********** .. image:: /images/Output.png :align: center This section contains all options for recording the audio file on hard drive. Click on the "File name" label to choose the name and repertory of the audio file to record. Then, two sliders allows you to choose the duration (in seconds) and the gain (in decibels) of the output file. Choose the desired number of audio channels with the "Channels" popup menu. The "Peak" bar indicates the maximum intensity of the processed audio signal. In the Output section, the toolbox at the far right presents three icons that are shortcuts to some features of Cecilia: .. image:: /images/Icones-Out.png In the Output section, as in the Input section, click on the *loudspeaker* to play the source sound file or on the *scissors* to edit the source sound file with an external application (see above). Click on the *arrows* to use the output sound file as the source sound. Post-Processing ----------------- The post-processing tab is situated below the transport panel, just beside the In/Out tab. .. image:: /images/Post-processing.png :align: center In this tab, you can add post-processing effects on the output audio file. It is possible to add up to 4 post-processing modules. Signal routing is from top to bottom (but the order can be changed with the little arrows in the top-right corner of each slot. Set audio parameters with the buttons on the left side. Choose the post-processing module in the "Effects" menu. The "Type" menu allows you to alternate between active module and bypass or to make a choice between different options, depending of the module. **Automations** All "plugin" parameters can be controlled through automations. To record an automation, *Double-Click* on the the little dot of the knob (it will turn red) and press play (in the transport panel) to start the playback. All knob variations will then be recorded and exposed in the grapher at the end of the playback, the dot will turn green. Afterwards, you can modify the automation curve in the graphic (see the corresponding following section). Another *Double-Click* will turn off both automation recording and playback. **Bindings** As with module's sliders below the grapher, post processing knobs can be controlled with MIDI controllers. Start the MIDI learn algorithm with a **Right-Click** on the knob. See *MIDI - OSC Control* for more details. Reverb ************** Simple reverb effect using the Freeverb algorithm. **Parameters** - *Mix*: dry/wet mix - *Time*: reverberation time in seconds - *Damp*: filtering of high frequencies In the "Type" menu, you can choose between activate and bypass the effect. WGVerb ************** Simple reverb effect using a network of eight interconnected waveguides. **Parameters** - *Mix*: dry/wet mix - *Feed*: depth of the reverb - *Cutoff*: lowpass cutoff in Hertz In the "Type" menu, you can choose between activate and bypass the effect. Filter *************** Variable state recursive second order filter. **Parameters** - *Level*: gain of the filtered signal - *Freq*: cutoff or center frequency of the filter - *Q*: Q factor/filter resonance. In the "Type" menu, you can choose between four types of filters : lowpass, highpass, bandpass and band reject. You can also select "bypass" to bypass the effect. Chorus *************** Delay-based chorus effect. **Parameters** - *Mix*: dry/wet mix - *Depth*: amplitude of the modulation - *Feed*: Amount of output signal fed back into the delay lines. In the "Type" menu, you can choose between activate and bypass the effect. Para EQ *********************** One band parametric equalizer. **Parameters** - *Freq*: cutoff or center frequency of the filter - *Q*: Q factor/filter resonance - *Gain*: intensity of the filtered signal, in decibels. In the "Type" menu, you can choose between three types of equalizers: Peak/Notch, Lowshelf and Highshelf. You can also select "bypass" to bypass the effect. 3 Bands EQ ******************* Three bands amplitude control. **Parameters** - *Low*: boost/cut, in dB, for a lowshelf with a cutoff at 250 Hz - *Mid*: boost/cut, in dB, for a peak/notch with a center frequency at 1500 Hz - *High*: boost/cut, in dB, for a highshelf with a cutoff at 2500 Hz In the "Type" menu, you can choose between activate and bypass the effect. Compress *************** Dynamic range reducer. **Parameters** - *Thresh*: compression threshold, in decibels - *Ratio*: compression ratio - *Gain*: intensity of the compressed signal, in decibels. In the "Type" menu, you can choose between activate and bypass the effect. Gate *************** A noise gates attenuates signals that register below a given threshold. **Parameters** - *Thresh*: in decibels - threshold below which the sound is attenuated - *Rise*: rise time or attack, in seconds - *Fall*: release time, in seconds. In the "Type" menu, you can choose between activate and bypass the effect. Disto *************** Arctangent distortion with lowpass filter. **Parameters** - *Drive*: intensity of the distorsion; from 0 - no distorsion - to 1 - square transfert fonction - *Slope*: normalized cutoff frequency of the low-pass filter; from 0 - no filter - to 1 - very low cutoff frequency - *Gain*: level of the distorted signal, in decibels. In the "Type" menu, you can choose between activate and bypass the effect. AmpMod ********************** Stereo amplitude modulation effect. **Parameters** - *Freq*: frequency of the modulating wave - *Amp*: amplitude of the modulating wave - *Stereo*: phase difference between the two stereo channels; from 0 - no phase difference - and 1 - left and right channels are 180 degrees out-of-phase. In the "Type" menu, you can choose between amplitude modulation (*Amplitude*) and ring modulation (*RingMod*) or bypass the effect. Phaser *************** Phasing effect based on all-pass filters that generates resonance peaks in the spectrum. **Parameters** - *Freq*: frequency of the first all-pass filter - *Q*: Q factor/filter resonance - *Spread*: spread factor - exponential operator that determinates the frequency of all other all-pass filters. In the "Type" menu, you can choose between activate and bypass the effect. Delay *************** Delay with feedback. **Parameters** - *Delay*: delay time, in seconds - *Feed*: feedback factor, between 0 and 1 - *Mix*: dry/wet mix. In the "Type" menu, you can choose between activate and bypass the effect. Flange *************** Swept comb filter effect. **Parameters** - *Depth*: amplitude of the LFO that modulates the delay. The modulation is set around a central time of 5 milliseconds - *Freq*: frequency of the modulating LFO - *Feed*: feedback factor - enhances the resonances in the spectrum. In the "Type" menu, you can choose between activate and bypass the effect. Harmonizer *************** Transpose the signal without changing its duration. **Parameters** - *Transpo*: transposition factor, in semi-tones - *Feed*: feedback factor - *Mix*: dry/wet mix. In the "Type" menu, you can choose between activate and bypass the effect. Resonators *************** Audio effect based on delays that generates harmonic resonances in the spectrum. **Parameters** - *Freq*: frequency of the first harmonic resonance - *Spread*: spread factor - exponential operator that determinates the frequency of all other harmonic resonances - *Mix*: dry/wet mix. In the "Type" menu, you can choose between activate and bypass the effect. DeadReson ********************* Similar to the Resonators effect. In this case, the harmonic resonances are slightly detuned. **Parameters** - *Freq*: frequency of the first harmonic resonance - *Detune*: detune of the other harmonic resonances - *Mix*: dry/wet mix. In the "Type" menu, you can choose between activate and bypass the effect. ChaosMod ********************* Amplitude modulation with a strange attractor as the waveform. **Parameters** - *Speed*: relative frequency of the oscillator - *Chaos*: control the periodicity of the waveform: 0 means nearly periodic, 1 means totally chaotic - *Amp*: amplitude of the modulating wave In the "Type" menu, you can choose between three attractors (*Lorenz*, *Rossler* and *ChenLee*) or bypass the effect. Presets -------------- The Presets panel allows you to save snapshots of the state of the current module and recall them as wanted. This is very useful to keep track of the work done within a module. Anytime a preset is added or removed from the popup, it is automatically saved (or deleted) to a file in cecilia5 resources folder (*~/.cecilia5/presets/moduleName/presetName*). Use to floppy disk to save a new preset and the "X" button to delete the currently loaded preset. Use the popup menu to select a preset to load. .. image:: /images/Presets.png :align: center Grapher ------------ The grapher is the central element of Cecilia. This is where the evolution of the module parameters over time will be defined. The toolbar above the grapher allows you to select the parameter to be edited, the behavior of the mouse or the desired curve generator. .. image:: /images/Grapher.png :align: center Here are the details of the available tools in the grapher's toolbar. .. image:: /images/GrapherPopup.png :align: left **Grapher popup** Use the popup to select a parameter line for editing. The chosen parameter will become front in the grapher and ready to be modified with the mouse or the generators. .. image:: /images/GrapherLineTools.png :align: left **Grapher line tools** Use these tools to manage the current parameter line on grapher. * Floppy disk - Save the current line parameters to the disk. * Folder - Load the current line parameters from disk. * Arrow - Reinitialize the current line parameters. * Eye - Show/Hide the current line on grapher. .. image:: /images/GrapherMouseBindings.png :align: left **Grapher's mouse bindings** This is the list of possible mouse interactions with the grapher. * Arrow - Use pointer tool (shortcut = "v") - Click and drag line to move it horizontally. - Double-click on line to toggle between curved and straight segments. - Click on point or drag to select points. - Click and drag to move point or selected points. - Holding Alt key when dragging clip the horizontal position. - Holding Shift+Alt key when dragging clip the vertical position. - Double-click anywhere to add point. - Delete key to delete selected points. * Pencil - Use pencil tool (shortcut = "p") - Click anywhere to add point. - Click and drag to add multiple points. * Magnifying glass - Use zoom tool (shortcut = "z") - Click and drag to zoom a region. - Escape key to reset zoom level. * Hand - Use hand tool (shortcut = "h") - When zoomed, click and drag to move view of the grapher. .. image:: /images/GrapherGenerators.png :align: left **Line generators** These tools open a window where controls can be set to fine-tune the chosen algorithm which generates points for the current line on the grapher. * Random line - Various stochastic function generators. * Sine wave - Various waveform function generators. * Gears - Various function processors which modify the current line's points. Sliders ------------ The sliders section shows most of the dynamic parameters of the module. Coloured sliders can be automated and the grapher contains a corresponding line for them. Grey sliders are either only active at initialization time or send their value on the mouse up event. They can't be automated. .. image:: /images/Sliders.png :align: center There is a label at the left of the slider. It shows the name of the parameter and has some mouse events bound to it. Here is the list: * Click on it to select the parameter in the grapher. * Shift-click to solo the parameter in the grapher. * Right-click to start midi learn on this slider. * Shift-Right-click to remove the current midi binding on the slider. * Double-click to set an OSC binding on this slider. The red circle and green triangle buttons can be used to record slider's movements in the grapher and play them back on a later run. The playback mode can be one of: * Dark green - Playback off. * Light green - Playback with visual update. * Yellow - Playback without visual update. The display at the right of the silder shows its current value. Click in to enter value from keyboard. Click and scroll on value to increment/decrement, left<->right position of the mouse on the value controls the increment size. Popups&Toggles ---------------- The Popups&Toggles section shows the discreet parameters of the module. here you will find tools to activate/deactivate processing parts, choose a filter type, define a waveform's harmonics, choose a compression format, etc. .. image:: /images/TogglesPopups.png :align: center cecilia5-5.4.1/doc-en/source/src/intro/000077500000000000000000000000001372272363700176025ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/intro/description.rst000066400000000000000000000026321372272363700226620ustar00rootroot00000000000000About Cecilia ================ .. image:: /images/Cecilia5_96.png :align: center .. centered:: ear-bending sonics for OSX, Windows & Linux Cecilia is an audio signal processing environment. Cecilia lets you create your own GUI (grapher, sliders, toggles, popup menus) using a simple syntax. Cecilia comes with many original built-in modules for sound effects and synthesis. Previously written in tcl/tk by Jean Piché and Alexandre Burton, Cecilia (version 4) was entirely rewritten in Python/wxPython and used the Python-Csound API for communicating between the interface and the audio engine. At this time, version 4.2 is the last release of version 4. Cecilia5 now uses Pyo, an audio engine written in C and created for the Python programming language. Pyo allows a much more powerful integration of the audio engine to the graphical interface. Since it is a standard python module, there is no need to use an API to communicate with the interface. Cecilia is free and open source (`GNU GPL v3 `_). Cecilia is programmed and maintained by Olivier Bélanger. External links ----------------- `Cecilia5 official web site `_ `Cecilia5 on github `_ `Cecilia5 bug tracker `_ `Pyo official web site `_ cecilia5-5.4.1/doc-en/source/src/intro/requirements.rst000066400000000000000000000027121372272363700230610ustar00rootroot00000000000000Installation - Requirements ============================ Cecilia is compatible with the following systems: - Mac OS X (10.12+) - Windows (7, 8, 8.1, 10) - Linux (at least Debian-based distros but should work with other linux flavours) Installing Cecilia on Windows or OSX --------------------------------------- Windows and OSX users can download self-contained binaries of the latest version of Cecilia `here `_. Older binaries for the 5.3.x serie can be downloaded on `github `_. Running Cecilia from the lastest sources ------------------------------------------- Before running Cecilia from the latest sources, please check if all these elements are installed on your computer: - `Python 3.7 `_ - `Pyo 1.0.1 `_ - `Numpy 1.18 `_ - `WxPython 4.0.7 `_ (install with `pip install wxPython`) - `Git client `_ Then, you can download Cecilia's sources by checking out the source code (in a terminal window): .. code-block:: bash git clone https://github.com/belangeo/cecilia5.git This will create a folder named "cecilia5" with the source code. Just go inside the folder and run Cecilia! .. code-block:: bash cd cecilia5 python3 Cecilia5.py cecilia5-5.4.1/doc-en/source/src/modules/000077500000000000000000000000001372272363700201175ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/modules/Dynamics/000077500000000000000000000000001372272363700216665ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/modules/Dynamics/Degrade.rst000066400000000000000000000031101372272363700237460ustar00rootroot00000000000000Degrade : Sampling rate and bit depth degradation with optional mirror clipping =============================================================================== Description ------------ This module allows the user to degrade a sound with artificial resampling and quantization. This process emulates the artifacts caused by a poor sampling frequency or bit depth resolution. It optionally offers a simple mirror distortion, if the degradation is not enough! Sliders -------- **Bit Depth** : Resolution of the amplitude in bits **Sampling Rate Ratio** : Ratio of the new sampling rate compared to the original one **Mirror Threshold** : Clipping limits between -1 and 1 (signal is reflected around the thresholds) **Filter Freq** : Center frequency of the filter **Filter Q** : Q factor of the filter **Dry / Wet** : Mix between the original signal and the degraded signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Filter Type** : Type of filter **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Chords** : Pitch interval between voices (chords), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Dynamics/Distortion.rst000066400000000000000000000026771372272363700245720ustar00rootroot00000000000000Distortion : Arctangent distortion module with pre and post filters =================================================================== Description ------------ This module applies an arctangent distortion with control on the amount of drive and pre/post filtering. Sliders -------- **Pre Filter Freq** : Center frequency of the filter applied before distortion **Pre Filter Q** : Q factor of the filter applied before distortion **Pre Gain** : Gain control applied before the distortion **Drive** : Amount of distortion applied on the signal **Post Filter Freq** : Center frequency of the filter applied after distortion **Post Filter Q** : Q factor of the filter applied after distortion Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Pre Filter Type** : Type of filter used before distortion **Post Filter Type** : Type of filter used after distortion **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Dynamics/DynamicsProcessor.rst000066400000000000000000000027171372272363700260760ustar00rootroot00000000000000DynamicsProcessor : Dynamic compression and gate module ======================================================= Description ------------ This module can be used to adjust the dynamic range of a signal by applying a compressor followed by a gate. Sliders -------- **Input Gain** : Adjust the amount of signal sent to the processing chain **Comp Thresh** : dB value at which the compressor becomes active **Comp Rise Time** : Time taken by the compressor to reach compression ratio **Comp Fall Time** : Time taken by the compressor to reach uncompressed state **Comp Knee** : Steepness of the compression curve **Gate Thresh** : dB value at which the gate becomes active **Gate Rise Time** : Time taken to open the gate **Gate Fall Time** : Time taken to close the gate **Output Gain** : Makeup gain applied after the processing chain Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Compression Ratio** : Ratio between the compressed signal and the uncompressed signal **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Dynamics/FeedbackLooper.rst000066400000000000000000000023541372272363700252710ustar00rootroot00000000000000FeedbackLooper : Frequency self-modulated sound looper ====================================================== Description ------------ This module loads a sound in a table and apply a frequency self-modulated playback of the content. A Frequency self-modulation occurs when the output sound of the playback is used to modulate the reading pointer speed. That produces new harmonics in a way similar to waveshaping distortion. Sliders -------- **Transposition** : Transposition, in cents, of the input sound **Feedback** : Amount of self-modulation in sound playback **Filter Frequency** : Frequency, in Hertz, of the filter **Filter Q** : Q of the filter (inverse of the bandwidth) Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Filter Type** : Type of the filter **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Chords** : Pitch interval between voices (chords), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Dynamics/UpDistoRes.rst000066400000000000000000000036021372272363700244620ustar00rootroot00000000000000UpDistoRes : Arctangent distortion module with upsampling and resonant lowpass filter ===================================================================================== Description ----------- This module applies an arctangent distortion on an upsampled signal and pass the result through a 24dB/oct lowpass resonant filter. Sliders ------- **Pre-Filter Freq** : Center frequency of the filter applied before distortion **Pre-Filter Q** : Q factor of the filter applied before distortion **Pre-Gain** : Gain control applied before the distortion **Drive** : Amount of distortion applied on the signal **Lowpass Freq** : Cutoff frequency of the 24dB/oct lowpass filter applied after distortion **Lowpass Res** : Resonance factor of the 24dB/oct lowpass filter applied after distortion **Dry / Wet** : Mix between the original signal and the degraded signal Graph Only ---------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ---------------- **Pre Filter Type** : Type of filter used before distortion **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Upsampling** : The resampling factor. The process will be applied with a virtual sampling rate of the current sampling rate times this factor. **Interpolation** : Defines the FIR lowpass kernel length used for interpolation and decimation. The kernel length will be the upsampling factor times this value. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Dynamics/WaveShaper.rst000066400000000000000000000017411372272363700244700ustar00rootroot00000000000000WaveShaper : Table lookup waveshaping module ============================================ Description ------------ This module applies a waveshaping-based distortion on the input sound. It allows the user to draw the transfert function on the screen. Sliders -------- **Filter Freq** : Center frequency of the post-process filter **Filter Q** : Q factor of the post-process filter Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Transfer Function** : Table used as transfert function for waveshaping Popups & Toggles ----------------- **Filter Type** : Type of the post-process filter **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Dynamics/index.rst000066400000000000000000000004101372272363700235220ustar00rootroot00000000000000Dynamics : Modules related to waveshaping and amplitude manipulations ===================================================================== .. toctree:: :maxdepth: 1 Degrade Distortion DynamicsProcessor FeedbackLooper WaveShaper UpDistoRes cecilia5-5.4.1/doc-en/source/src/modules/Filters/000077500000000000000000000000001372272363700215275ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/modules/Filters/AMFMFilter.rst000066400000000000000000000027641372272363700241600ustar00rootroot00000000000000AMFMFilter : AM/FM modulated filter =================================== Description ------------ The input sound is filtered by a variable type modulated filter. Speed, depth and shape can be modified for both AM and FM modulators. Sliders -------- **Filter Mean Freq** : Mean frequency of the filter **Resonance** : Q factor of the filter **AM Depth** : Amplitude of the amplitude modulator **AM Freq** : Speed, in Hz, of the amplitude modulator **FM Depth** : Amplitude of the frequency modulator **FM Freq** : Speed, in Hz, of the frequency modulator **Mod Sharpness** : Sharpness of waveforms used as modulators **Dry / Wet** : Mix between the original signal and the filtered signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Filter Type** : Type of filter **AM Mod Type** : Shape of the amplitude modulator **FM Mod Type** : Shape of the frequency modulator **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Filters/BrickWall.rst000066400000000000000000000025511372272363700241360ustar00rootroot00000000000000BrickWall : Convolution brickwall lowpass/highpass/bandpass/bandstop filter =========================================================================== Description ------------ Convolution filter with a user-defined length sinc kernel. This kind of filters are very CPU expensive but can give quite good stopband attenuation. Sliders -------- **Cutoff Frequency** : Cutoff frequency, in Hz, of the filter. **Bandwidth** : Bandwith, in Hz, of the filter. Used only by bandpass and pnadstop filters. **Filter Order** : Number of points of the filter kernel. A longer kernel means a sharper attenuation (and a higher CPU cost). This value is only available at initialization time. Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Filter Type** : Type of the filter (lowpass, highpass, bandpass, bandstop) **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Filters/MaskFilter.rst000066400000000000000000000023141372272363700243220ustar00rootroot00000000000000MaskFilter : Ranged filter module using lowpass and highpass filters ==================================================================== Description ------------ The signal is first lowpassed and then highpassed to create a bandpass filter with independant lower and higher boundaries. The user can interpolate between two such filters. Sliders -------- **Filter 1 Limits** : Range of the first filter (min = highpass, max = lowpass) **Filter 2 Limits** : Range of the second filter (min = highpass, max = lowpass) **Mix** : Balance between filter 1 and filter 2 Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Number of Stages** : Amount of stacked biquad filters **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Filters/ParamEQ.rst000066400000000000000000000031511372272363700235470ustar00rootroot00000000000000ParamEQ : Parametric equalizer ============================== Description ------------ Standard parametric equalizer built with four lowshelf/highshelf/peak/notch filters. Sliders -------- **Freq 1 Boost/Cut** : Gain of the first EQ **Freq 1** : Center frequency of the first EQ **Freq 1 Q** : Q factor of the first EQ **Freq 2 Boost/Cut** : Gain of the second EQ **Freq 2** : Center frequency of the second EQ **Freq 2 Q** : Q factor of the second EQ **Freq 3 Boost/Cut** : Gain of the third EQ **Freq 3** : Center frequency of the third EQ **Freq 3 Q** : Q factor of the third EQ **Freq 5 Boost/Cut** : Gain of the fourth EQ **Freq 4** : Center frequency of the fourth EQ **Freq 5 Q** : Q factor of the fourth EQ Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **EQ Type 1** : EQ type of the first EQ **EQ Type 2** : EQ type of the second EQ **EQ Type 3** : EQ type of the third EQ **EQ Type 4** : EQ type of the fourth EQ **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Filters/Phaser.rst000066400000000000000000000023561372272363700235110ustar00rootroot00000000000000Phaser : Multi-stages second-order phase shifter allpass filters ================================================================ Description ------------ Phaser implements a multi-stages second-order allpass filters, which, when mixed with the original signal, create a serie of peaks/notches in the sound. Sliders -------- **Base Freq** : Center frequency of the first notch of the phaser **Q Factor** : Q factor (resonance) of the phaser notches **Notch Spread** : Distance between phaser notches **Feedback** : Amount of phased signal fed back into the phaser **Dry / Wet** : Mix between the original signal and the phased signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Number of Stages** : Changes notches bandwidth (stacked filters), only available at initialization time **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Filters/StateVar.rst000066400000000000000000000021771372272363700240210ustar00rootroot00000000000000StateVar : State Variable Filter ================================ Description ------------ This module implements lowpass, bandpass and highpass filters in parallel and allow the user to interpolate on an axis lp -> bp -> hp. Sliders -------- **Cutoff/Center Freq** : Cutoff frequency for lp and hp (center freq for bp) **Filter Q** : Q factor (inverse of bandwidth) of the filter **Type (lp->bp->hp)** : Interpolating factor between filters **Dry / Wet** : Mix between the original and the filtered signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Chords** : Pitch interval between voices (chords), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Filters/Vocoder.rst000066400000000000000000000037241372272363700236700ustar00rootroot00000000000000Vocoder : Time domain vocoder effect ==================================== Description ------------ Applies the spectral envelope of a first sound to the spectrum of a second sound. The vocoder is an analysis/synthesis system, historically used to reproduce human speech. In the encoder, the first input (spectral envelope) is passed through a multiband filter, each band is passed through an envelope follower, and the control signals from the envelope followers are communicated to the decoder. The decoder applies these (amplitude) control signals to corresponding filters modifying the second source (exciter). Sliders -------- **Base Frequency** : Center frequency of the first band. This is the base frequency used tocompute the upper bands. **Frequency Spread** : Spreading factor for upper band frequencies. Each band is `freq * pow(order, spread)`, where order is the harmonic rank of the band. **Q Factor** : Q of the filters as `center frequency / bandwidth`. Higher values imply more resonance around the center frequency. **Time Response** : Time response of the envelope follower. Lower values mean smoother changes, while higher values mean a better time accuracy. **Gain** : Output gain of the process in dB. **Num of Bands** : The number of bands in the filter bank. Defines the number of notches in the spectrum. Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Filters/index.rst000066400000000000000000000003411372272363700233660ustar00rootroot00000000000000Filters : Filtering and subtractive synthesis modules ===================================================== .. toctree:: :maxdepth: 1 AMFMFilter BrickWall MaskFilter ParamEQ Phaser StateVar Vocoder cecilia5-5.4.1/doc-en/source/src/modules/Multiband/000077500000000000000000000000001372272363700220365ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/modules/Multiband/MultiBandBeatMaker.rst000066400000000000000000000043521372272363700262270ustar00rootroot00000000000000MultiBandBeatMaker : Multi-band algorithmic beatmaker ===================================================== Description ------------ MultiBandBeatMaker uses four algorithmic beat generators to play spectral separated chunks of a sound. Sliders -------- **Frequency Splitter** : Split points for multi-band processing **Num of Taps** : Number of taps in a measure **Tempo** : Speed of taps **Tap Length** : Length of taps **Beat 1 Index** : Soundfile index of the first beat **Beat 2 Index** : Soundfile index of the second beat **Beat 3 Index** : Soundfile index of the third beat **Beat 4 Index** : Soundfile index of the fourth beat **Beat 1 Distribution** : Repartition of taps for the first beat (100% weak --> 100% down) **Beat 2 Distribution** : Repartition of taps for the second beat (100% weak --> 100% down) **Beat 3 Distribution** : Repartition of taps for the third beat (100% weak --> 100% down) **Beat 4 Distribution** : Repartition of taps for the fourth beat (100% weak --> 100% down) **Beat 1 Gain** : Gain of the first beat **Beat 2 Gain** : Gain of the second beat **Beat 3 Gain** : Gain of the third beat **Beat 4 Gain** : Gain of the fourth beat **Global Seed** : Seed value for the algorithmic beats, using the same seed with the same distributions will yield the exact same beats Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Beat 1 ADSR** : Envelope of taps for the first beat in breakpoint fashion **Beat 2 ADSR** : Envelope of taps for the second beat in breakpoint fashion **Beat 3 ADSR** : Envelope of taps for the third beat in breakpoint fashion **Beat 4 ADSR** : Envelope of taps for the fourth beat in breakpoint fashion Popups & Toggles ----------------- **Polyphony per Voice** : The number of streams used for each voice's polpyhony. High values allow more overlaps but are more CPU expensive, only available at initialization time. cecilia5-5.4.1/doc-en/source/src/modules/Multiband/MultiBandDelay.rst000066400000000000000000000033001372272363700254220ustar00rootroot00000000000000MultiBandDelay : Multi-band delay with feedback =============================================== Description ------------ MultiBandDelay implements four separated spectral band delays with independent delay time, gain and feedback. Sliders -------- **Frequency Splitter** : Split points for multi-band processing **Delay Band 1** : Delay time for the first band **Feedback Band 1** : Amount of delayed signal fed back into the first band delay **Gain Band 1** : Gain of the delayed first band **Delay Band 2** : Delay time for the second band **Feedback Band 2** : Amount of delayed signal fed back into the second band delay **Gain Band 2** : Gain of the delayed second band **Delay Band 3** : Delay time for the third band **Feedback Band 3** : Amount of delayed signal fed back into the third band delay **Gain Band 3** : Gain of the delayed third band **Delay Band 4** : Delay time for the fourth band **Feedback Band 4** : Amount of delayed signal fed back into the fourth band delay **Gain Band 4** : Gain of the delayed fourth band **Dry / Wet** : Mix between the original signal and the delayed signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Multiband/MultiBandDisto.rst000066400000000000000000000032211372272363700254500ustar00rootroot00000000000000MultiBandDisto : Multi-band distortion module ============================================= Description ------------ MultiBandDisto implements four separated spectral band distortions with independent drive, slope and gain. Sliders -------- **Frequency Splitter** : Split points for multi-band processing **Band 1 Drive** : Amount of distortion applied on the first band **Band 1 Slope** : Harshness of distorted first band **Band 1 Gain** : Gain of the distorted first band **Band 2 Drive** : Amount of distortion applied on the second band **Band 2 Slope** : Harshness of distorted second band **Band 2 Gain** : Gain of the distorted second band **Band 3 Drive** : Amount of distortion applied on the third band **Band 3 Slope** : Harshness of distorted third band **Band 3 Gain** : Gain of the distorted third band **Band 4 Drive** : Amount of distortion applied on the fourth band **Band 4 Slope** : Harshness of distorted fourth band **Band 4 Gain** : Gain of the distorted fourth band **Dry / Wet** : Mix between the original signal and the delayed signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Multiband/MultiBandFreqShift.rst000066400000000000000000000025641372272363700262720ustar00rootroot00000000000000MultiBandFreqShift : Multi-band frequency shifter module ======================================================== Description ------------ MultiBandFreqShift implements four separated spectral band frequency shifters with independent amount of shift and gain. Sliders -------- **Frequency Splitter** : Split points for multi-band processing **Freq Shift Band 1** : Shift frequency of first band **Gain Band 1** : Gain of the shifted first band **Freq Shift Band 2** : Shift frequency of second band **Gain Band 2** : Gain of the shifted second band **Freq Shift Band 3** : Shift frequency of third band **Gain Band 3** : Gain of the shifted third band **Freq Shift Band 4** : Shift frequency of fourth band **Gain Band 5** : Gain of the shifted fourth band **Dry / Wet** : Mix between the original signal and the shifted signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Multiband/MultiBandGate.rst000066400000000000000000000030601372272363700252470ustar00rootroot00000000000000MultiBandGate : Multi-band noise gate module ============================================ Description ------------ MultiBandGate implements four separated spectral band noise gaters with independent threshold and gain. Sliders -------- **Frequency Splitter** : Split points for multi-band processing **Threshold Band 1** : dB value at which the gate becomes active on the first band **Gain Band 1** : Gain of the gated first band **Threshold Band 2** : dB value at which the gate becomes active on the second band **Gain Band 2** : Gain of the gated second band **Threshold Band 3** : dB value at which the gate becomes active on the third band **Gain Band 3** : Gain of the gated third band **Threshold Band 4** : dB value at which the gate becomes active on the fourth band **Gain Band 4** : Gain of the gated fourth band **Rise Time** : Time taken by the gate to close **Fall Time** : Time taken by the gate to open **Dry / Wet** : Mix between the original signal and the shifted signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Multiband/MultiBandHarmonizer.rst000066400000000000000000000035251372272363700265130ustar00rootroot00000000000000MultiBandHarmonizer : Multi-band harmonizer module ================================================== Description ------------ MultiBandHarmonizer implements four separated spectral band harmonizers with independent transposition, feedback and gain. Sliders -------- **Frequency Splitter** : Split points for multi-band processing **Transpo Band 1** : Pitch shift for the first band **Feedback Band 1** : Amount of harmonized signal fed back into the first band harmonizer **Gain Band 1** : Gain of the harmonized first band **Transpo Band 2** : Pitch shift for the second band **Feedback Band 2** : Amount of harmonized signal fed back into the second band harmonizer **Gain Band 2** : Gain of the harmonized second band **Transpo Band 3** : Pitch shift for the third band **Feedback Band 3** : Amount of harmonized signal fed back into the third band harmonizer **Gain Band 3** : Gain of the harmonized third band **Transpo Band 4** : Pitch shift for the fourth band **Feedback Band 4** : Amount of harmonized signal fed back into the fourth band harmonizer **Gain Band 4** : Gain of the harmonized fourth band **Dry / Wet** : Mix between the original signal and the harmonized signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Win Size** : Harmonizer window size (delay) in seconds **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Multiband/MultiBandReverb.rst000066400000000000000000000035611372272363700256220ustar00rootroot00000000000000MultiBandReverb : Multi-band reverberation module ================================================= Description ------------ MultiBandReverb implements four separated spectral band harmonizers with independent reverb time, cutoff and gain. Sliders -------- **Frequency Splitter** : Split points for multi-band processing **Reverb Time 1** : Amount of reverb (tail duration) applied on first band **Lowpass Cutoff 1** : Cutoff frequency of the reverb's lowpass filter (damp) for the first band **Gain 1** : Gain of the reverberized first band **Reverb Time 2** : Amount of reverb (tail duration) applied on second band **Lowpass Cutoff 2** : Cutoff frequency of the reverb's lowpass filter (damp) for the second band **Gain 2** : Gain of the reverberized second band **Reverb Time 3** : Amount of reverb (tail duration) applied on third band **Lowpass Cutoff 3** : Cutoff frequency of the reverb's lowpass filter (damp) for the third band **Gain 3** : Gain of the reverberized third band **Reverb Time 4** : Amount of reverb (tail duration) applied on fourth band **Lowpass Cutoff 4** : Cutoff frequency of the reverb's lowpass filter (damp) for the fourth band **Gain 4** : Gain of the reverberized fourth band **Dry / Wet** : Mix between the original signal and the harmonized signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Multiband/index.rst000066400000000000000000000005111372272363700236740ustar00rootroot00000000000000Multiband : Various processing applied independently to four spectral regions ============================================================================== .. toctree:: :maxdepth: 1 MultiBandBeatMaker MultiBandDelay MultiBandDisto MultiBandFreqShift MultiBandGate MultiBandHarmonizer MultiBandReverb cecilia5-5.4.1/doc-en/source/src/modules/Pitch/000077500000000000000000000000001372272363700211665ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/modules/Pitch/ChordMaker.rst000066400000000000000000000034601372272363700237420ustar00rootroot00000000000000ChordMaker : Sampler-based harmonizer module with multiple voices ================================================================= Description ------------ The input sound is mixed with five real-time, non-stretching, harmonization voices. Sliders -------- **Transpo Voice 1** : Pitch shift of the first voice **Gain Voice 1** : Gain of the transposed first voice **Transpo Voice 2** : Pitch shift of the second voice **Gain Voice 2** : Gain of the transposed second voice **Transpo Voice 3** : Pitch shift of the third voice **Gain Voice 3** : Gain of the transposed third voice **Transpo Voice 4** : Pitch shift of the fourth voice **Gain Voice 4** : Gain of the transposed fourth voice **Transpo Voice 5** : Pitch shift of the fifth voice **Gain Voice 5** : Gain of the transposed fifth voice **Feedback** : Amount of transposed signal fed back into the harmonizers (feedback is voice independent) **Dry / Wet** : Mix between the original signal and the harmonized signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Win Size** : Harmonizer window size in seconds **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. # Voice Activation (1 --> 5) Mute or unmute each voice independently **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Pitch/FreqShift.rst000066400000000000000000000033171372272363700236170ustar00rootroot00000000000000FreqShift : Two frequency shifters with optional cross-delay and feedback ========================================================================= Description ------------ This module implements two frequency shifters from which the output sound of both one can be fed back in the input of the other. Cross- feedback occurs after a user-defined delay of the output sounds. Sliders -------- **Filter Freq** : Cutoff or center frequency of the pre-filtering stage **Filter Q** : Q factor of the pre-filtering stage **Frequency Shift 1** : Frequency shift, in Hz, of the first voice **Frequency Shift 2** : Frequency shift, in Hz, of the second voice **Feedback Delay** : Delay time before the signal is fed back into the delay lines **Feedback** : Amount of signal fed back into the delay lines **Feedback Gain** : Amount of delayed signal cross-fed back into the frequency shifters. Signal from delay 1 into shifter 2 and signal from delay 2 into shifter 1. **Dry / Wet** : Mix between the original signal and the shifted signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Filter Type** : Type of filter **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Pitch/Harmonizer.rst000066400000000000000000000023501372272363700240360ustar00rootroot00000000000000Harmonizer : Two voices harmonization module ============================================= Description ------------ This module implements a two voices harmonization with independent feedback. Sliders -------- **Transpo Voice 1** : Pitch shift of the first voice **Transpo Voice 2** : Pitch shift of the second voice **Feedback** : Amount of transposed signal fed back into the harmonizers (feedback is voice independent) **Harm1 / Harm2** : Mix between the first and the second harmonized signals **Dry / Wet** : Mix between the original signal and the harmonized signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Win Size Voice 1** : Window size of the first harmonizer (delay) in second **Win Size Voice 2** : Window size of the second harmonizer (delay) in second **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Pitch/LooperBank.rst000066400000000000000000000044021372272363700237540ustar00rootroot00000000000000LooperBank : Sound looper bank with independant pitch and amplitude random ========================================================================== Description ------------ Huge oscillator bank (up to 500 players) looping a soundfile stored in a waveform table. The frequencies and amplitudes can be modulated by two random generators with interpolation (each partial have a different set of randoms). Sliders -------- **Transposition** : Transposition of the base frequency of the process, given by the sound length. **Frequency Spread** : Coefficient of expansion used to compute partial frequencies. If 0, all partials will be at the base frequency. A value of 1 will generate integer harmonics, a value of 2 will skip even harmonics and non-integer values will generate different series of inharmonic frequencies. **Amplitude Slope** : Specifies the multiplier in the series of amplitude coefficients. **Boost / Cut** : Controls the overall amplitude. **Freq Rand Speed** : Frequency, in cycle per second, of the frequency modulations. **Freq Rand Gain** : Maximum frequency deviation (positive and negative) in portion of the partial frequency. A value of 1 means that the frequency can drift from 0 Hz to twice the partial frequency. A value of 0 deactivates the frequency deviations. **Amp Rand Speed** : Frequency, in cycle per second, of the amplitude modulations. **Amp Rand Gain** : Amount of amplitude deviation. 0 deactivates the amplitude modulations and 1 gives full amplitude modulations. **Voices Per Channel** : Number of loopers created for each output channel. Changes will be effective on next start. Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Partials Freq Jitter** : If active, a small jitter is added to the frequency of each partial. For a large number of oscillators and a very small `Frequency Spread`, the periodicity between partial frequencies can cause very strange artefact. Adding a jitter breaks the periodicity. cecilia5-5.4.1/doc-en/source/src/modules/Pitch/LooperMod.rst000066400000000000000000000026241372272363700236240ustar00rootroot00000000000000LooperMod : Looper module with optional amplitude and frequency modulation ========================================================================== Description ------------ A simple soundfile looper with optional amplitude and frequency modulations of variable shapes. Sliders -------- **AM Range** : Minimum and maximum amplitude of the Amplitude Modulation LFO **AM Speed** : Frequency of the Amplitude Modulation LFO **FM Range** : Minimum and maximum amplitude of the Frequency Modulation LFO **FM Speed** : Frequency of the Frequency Modulation LFO **Dry / Wet** : Mix between the original signal and the processed signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **AM LFO Type** : Shape of the amplitude modulation waveform **AM On/Off** : Activate or deactivate the amplitude modulation **FM LFO Type** : Shape of the frequency modulation waveform **FM On/Off** : Activate or deactivate frequency modulation **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Pitch/PitchLooper.rst000066400000000000000000000031041372272363700241460ustar00rootroot00000000000000PitchLooper : Table-based transposition module with multiple voices =================================================================== Description ------------ This module implements five sound loopers playing in parallel. Loopers are table-based so a change in pitch will produce a change in sound duration. This can be useful to create rhythm effects as well as harmonic effects. Sliders -------- **Transpo Voice 1** : Playback speed of the first voice **Gain Voice 1** : Gain of the transposed first voice **Transpo Voice 2** : Playback speed of the second voice **Gain Voice 2** : Gain of the transposed second voice **Transpo Voice 3** : Playback speed of the third voice **Gain Voice 3** : Gain of the transposed third voice **Transpo Voice 4** : Playback speed of the fourth voice **Gain Voice 4** : Gain of the transposed fourth voice **Transpo Voice 5** : Playback speed of the fifth voice **Gain Voice 5** : Gain of the transposed fifth voice Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Voice Activation 1 --> 5** : Mute or unmute each voice independently **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Pitch/RandomAccumulator.rst000066400000000000000000000046161372272363700253470ustar00rootroot00000000000000RandomAccumulator : Variable speed recording accumulator module =============================================================== Description ----------- This module records the sound from the sampler in a table at a varying position and speed. The feedback parameter indicates how much of the previous recording at the current position is kept back in the table. The recorded table is read in loop at the normal speed, with possibility to activate a 180 degrees out-of-phase overlap. Sliders ------- **Pre-Filter Freq** : Center frequency of the filter applied before the recording **Pre-Filter Q** : Q factor of the filter applied before the recording **Accum Feedback** : The amount of previous signal in the table that is kept back, mixed with the new recording. **Record Time Rand** : The time it took to the recording head to reach the new position target in the table. The target is chosen randomly between 0 and the end of the table. If the time is longer than the distance to run, the signal will be write slowly, so the playback (at regular speed) will give an upward transposition. When the target is reached, a new target and a new recording duration are chosen. **Buffer Length** : The size of the table in seconds. This parameter is updated only at the start of the performance. **Global Seed** : Root of stochatic generators. If 0, a new value is chosen randomly each time the performance starts. Otherwise, the same root is used every performance, making the generated sequences the same every time. Graph Only ---------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Recording Envelope** : The envelope applied to each recording segment Popups & Toggles ---------------- **Pre-Filter Type** : Type of filter used before the recording **Overlapped** : If "On", a second player, 180 degrees out-of-phase, will read the recorded buffer. The signals of both players are summed and sent to the output **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Pitch/index.rst000066400000000000000000000004141372272363700230260ustar00rootroot00000000000000Pitch : Modules related to playback speed and pitch manipulations ================================================================= .. toctree:: :maxdepth: 1 ChordMaker FreqShift Harmonizer LooperBank LooperMod PitchLooper RandomAccumulator cecilia5-5.4.1/doc-en/source/src/modules/Resonators&Verbs/000077500000000000000000000000001372272363700233265ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/modules/Resonators&Verbs/ConvolutionReverb.rst000066400000000000000000000015461372272363700275530ustar00rootroot00000000000000ConvolutionReverb : FFT-based convolution reverb ================================================ Description ------------ This module implements a convolution based on a uniformly partitioned overlap-save algorithm. It can be used to convolve an input signal with an impulse response soundfile to simulate real acoustic spaces. Sliders -------- **Dry / Wet** : Mix between the original signal and the convoluted signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Resonators&Verbs/Convolve.rst000066400000000000000000000024671372272363700256640ustar00rootroot00000000000000Convolve : Circular convolution filtering module ================================================ Description ------------ Circular convolution filter where the filter kernel is extract from a soundfile segments. Circular convolution is very expensive to compute, so the impulse response must be kept very short to run in real time. Sliders -------- **Impulse index 1** : Position of the first impulse response in the soundfile (mouse up) **Impulse index 2** : Position of the second impulse response in the soundfile (mouse up) **Imp 1 <--> Imp 2** : Morphing between the two impulse responses **Dry / Wet** : Mix between the original signal and the convoluted signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Size** : Buffer size of the convolution **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Resonators&Verbs/DetunedResonators.rst000066400000000000000000000041041372272363700275270ustar00rootroot00000000000000DetunedResonators : Eight detuned resonators with jitter control ================================================================ Description ------------ This module implements a detuned resonator (waveguide) bank with random controls. This waveguide model consists of one delay-line with a 3-stages recursive allpass filter which made the resonances of the waveguide out of tune. Sliders -------- **Pitch Offset** : Base pitch of all the resonators **Amp Random Amp** : Amplitude of the jitter applied on the resonators amplitude **Amp Random Speed** : Frequency of the jitter applied on the resonators amplitude **Delay Random Amp** : Amplitude of the jitter applied on the resonators delay (pitch) **Delay Random Speed** : Frequency of the jitter applied on the resonators delay (pitch) **Detune Factor** : Detune spread of the resonators **Pitch Voice 1** : Pitch of the first resonator **Pitch Voice 2** : Pitch of the second resonator **Pitch Voice 3** : Pitch of the third resonator **Pitch Voice 4** : Pitch of the fourth resonator **Pitch Voice 5** : Pitch of the fifth resonator **Pitch Voice 6** : Pitch of the sixth resonator **Pitch Voice 7** : Pitch of the seventh resonator **Pitch Voice 8** : Pitch of the eigth resonator **Feedback** : Amount of resonators fed back in the resonators **Dry / Wet** : Mix between the original signal and the resonators Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Voice Activation ( 1 --> 8 )** : Mute or unmute each of the eigth voices independently **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Resonators&Verbs/Resonators.rst000066400000000000000000000036671372272363700262330ustar00rootroot00000000000000Resonators : Eight resonators with jitter control ================================================= Description ------------ This module implements a resonator (waveguide) bank with random controls. This waveguide model consists of one delay-line with a simple lowpass filtering and lagrange interpolation. Sliders -------- **Pitch Offset** : Base pitch of all the resonators **Amp Random Amp** : Amplitude of the jitter applied on the resonators amplitude **Amp Random Speed** : Frequency of the jitter applied on the resonators amplitude **Delay Random Amp** : Amplitude of the jitter applied on the resonators delay (pitch) **Delay Random Speed** : Frequency of the jitter applied on the resonators delay (pitch) **Pitch Voice 1** : Pitch of the first resonator **Pitch Voice 2** : Pitch of the second resonator **Pitch Voice 3** : Pitch of the third resonator **Pitch Voice 4** : Pitch of the fourth resonator **Pitch Voice 5** : Pitch of the fifth resonator **Pitch Voice 6** : Pitch of the sixth resonator **Pitch Voice 7** : Pitch of the seventh resonator **Pitch Voice 8** : Pitch of the eigth resonator **Feedback** : Amount of resonators fed back in the resonators **Dry / Wet** : Mix between the original signal and the resonators Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Voice Activation ( 1 --> 8 )** : Mute or unmute each of the eigth voices independently **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Resonators&Verbs/WGuideBank.rst000066400000000000000000000030401372272363700260350ustar00rootroot00000000000000WGuideBank : Multiple waveguide models module ============================================= Description ------------ Resonators whose frequencies are controlled with an expansion expression. freq[i] = BaseFreq * pow(WGExpansion, i) Sliders -------- **Base Freq** : Base pitch of the waveguides **WG Expansion** : Spread between waveguides **WG Feedback** : Length of the waveguides **WG Filter** : Center frequency of the filter **Amp Deviation Amp** : Amplitude of the jitter applied on the waveguides amplitude **Amp Deviation Speed** : Frequency of the jitter applied on the waveguides amplitude **Freq Deviation Amp** : Amplitude of the jitter applied on the waveguides pitch **Freq Deviation Speed** : Frequency of the jitter applied on the waveguides pitch **Dry / Wet** : Mix between the original signal and the waveguides Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Filter Type** : Type of the post-process filter **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Resonators&Verbs/index.rst000066400000000000000000000003421372272363700251660ustar00rootroot00000000000000Resonators&Verbs : Artificial spaces generation modules ======================================================= .. toctree:: :maxdepth: 1 ConvolutionReverb Convolve DetunedResonators Resonators WGuideBank cecilia5-5.4.1/doc-en/source/src/modules/Spectral/000077500000000000000000000000001372272363700216745ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/modules/Spectral/AddResynth.rst000066400000000000000000000023271372272363700244770ustar00rootroot00000000000000AddResynth : Phase vocoder additive resynthesis =============================================== Description ------------ This module uses the magnitudes and true frequencies of a phase vocoder analysis to control amplitudes and frequencies envelopes of an oscillator bank. Sliders -------- **Transpo** : Transposition factor **Num of Oscs** : Number of oscillators used to synthesize the sound **First bin** : First synthesized bin **Increment** : Step between synthesized bins **Dry / Wet** : Mix between the original signal and the processed signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/BinModulator.rst000066400000000000000000000040541372272363700250300ustar00rootroot00000000000000BinModulator : Frequency independent amplitude and frequency modulations ======================================================================== Description ------------ This module modulates both the amplitude and the frequency of each bin from a phase vocoder analysis with an independent oscillator. Power series are used to compute modulating oscillator frequencies. Sliders -------- **AM Base Freq** : Base amplitude modulation frequency, in Hertz. **AM Spread** : Spreading factor for AM oscillator frequencies. 0 means every oscillator has the same frequency. **FM Base Freq** : Base frequency modulation frequency, in Hertz. **FM Spread** : Spreading factor for FM oscillator frequencies. 0 means every oscillator has the same frequency. **FM Depth** : Amplitude of the modulating oscillators. **Dry / Wet** : Mix between the original signal and the delayed signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Reset** : On mouse down, this button reset the phase of all bin's oscillators to 0. **Routing** : Path of the sound **AM Shape** : Waveform of the amplitude modulators. Possible shapes are: Sine, Sawtooth, Ramp, Square, Triangle, Brown Noise, Pink Noise, White Noise **FM Shape** : Waveform of the frequency modulators. Possible shapes are: Sine, Sawtooth, Ramp, Square, Triangle, Brown Noise, Pink Noise, White Noise **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/BinWarper.rst000066400000000000000000000025201372272363700243160ustar00rootroot00000000000000BinWarper : Phase vocoder buffer with bin independent speed playback ==================================================================== Description ------------ This module pre-analyses the input sound and keeps the phase vocoder frames in a buffer for the playback. User has control on playback position independently for every frequency bin. Sliders -------- **Low Bin Speed** : Lowest bin speed factor **High Bin Speed** : Highest bin speed factor * For random distribution, these values are the minimum and the maximum of the distribution. Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Reset** : Reset pointer positions to the beginning of the buffer. **Speed Distribution** : Speed distribution algorithm. **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/CrossSynth.rst000066400000000000000000000021561372272363700245510ustar00rootroot00000000000000CrossSynth : Phase Vocoder based cross synthesis module ======================================================= Description ------------ Multiplication of magnitudes from two phase vocoder streaming objects. Sliders -------- **Exciter Pre Filter Freq** : Frequency of the pre-FFT filter **Exciter Pre Filter Q** : Q of the pre-FFT filter **Dry / Wet** : Mix between the original signal and the processed signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Exc Pre Filter Type** : Type of the pre-FFT filter **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/CrossSynth2.rst000066400000000000000000000025131372272363700246300ustar00rootroot00000000000000CrossSynth2 : Phase Vocoder based cross synthesis module ======================================================== Description ------------ Performs cross-synthesis between two phase vocoder streaming objects. The amplitudes from `Source Exciter` and `Spectral Envelope`, scaled by `Crossing Index`, are applied to the frequencies of `Source Exciter`. Sliders -------- **Crossing Index** : Morphing index between the two source's magnitudes **Exc Pre Filter Freq** : Frequency of the pre-FFT filter **Exc Pre Filter Q** : Q of the pre-FFT filter **Dry / Wet** : Mix between the original signal and the processed signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Exc Pre Filter Type** : Type of the pre-FFT filter **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/Morphing.rst000066400000000000000000000021561372272363700242150ustar00rootroot00000000000000Morphing : Phase Vocoder based morphing module ============================================== Description ------------ This module performs spectral morphing between two phase vocoder analysis. According to `Morphing Index`, the amplitudes from two PV analysis are interpolated linearly while the frequencies are interpolated exponentially. Sliders -------- **Morphing Index** : Morphing index between the two sources **Dry / Wet** : Mix between the original signal and the morphed signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/Shifter.rst000066400000000000000000000017511372272363700240360ustar00rootroot00000000000000Shifter : Phase Vocoder based frequency shifters ================================================ Description ------------ This module implements two voices that linearly moves the analysis bins by the amount, in Hertz, specified by the the `Shift 1` and `Shift 2` parameters. Sliders -------- **Shift 1** : Shifting, in Hertz, of the first voice **Shift 2** : Shifting, in Hertz, of the second voice **Dry / Wet** : Mix between the original signal and the delayed signals Popups & Toggles ----------------- **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/SpectralDelay.rst000066400000000000000000000025311372272363700251630ustar00rootroot00000000000000SpectralDelay : Phase vocoder spectral delay ============================================ Description ------------ This module applies different delay times and feedbacks for each bin of a phase vocoder analysis. Delay times and feedbacks are specified via grapher functions. Sliders -------- **Max Delay** : Maximum delay time per bin. Used to allocate memories. only available at initialization time **Dry / Wet** : Mix between the original signal and the delayed signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Bin Delays** : Controls the delay time for every bin, from left (low freq) to right (high freq) on the graph **Bin Feedbacks** : Controls the feedback factor for every bin, from left to right on the graph Popups & Toggles ----------------- **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/SpectralFilter.rst000066400000000000000000000020301372272363700253440ustar00rootroot00000000000000SpectralFilter : Phase Vocoder based filter =========================================== Description ------------ This module filters frequency components of a phase vocoder analysed sound according to the shape drawn in the grapher function. Sliders -------- **Dry / Wet** : Mix between the original signal and the processed signal Graph Only ----------- **Spectral Filter** : Shape of the filter (amplitude of analysis bins) **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/SpectralGate.rst000066400000000000000000000020551372272363700250060ustar00rootroot00000000000000SpectralGate : Spectral gate (Phase Vocoder) ============================================ Description ------------ For each frequency band of a phase vocoder analysis, if the amplitude of the bin falls below a given threshold, it is attenuated according to the `Gate Attenuation` parameter. Sliders -------- **Gate Threshold** : dB value at which the gate becomes active **Gate Attenuation** : Gain in dB of the gated signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/SpectralWarper.rst000066400000000000000000000022221372272363700253620ustar00rootroot00000000000000SpectralWarper : Phase Vocoder buffer and playback with transposition ===================================================================== Description ------------ This module pre-analyses the input sound and keeps the phase vocoder frames in a buffer for the playback. User has control on playback position and transposition. Sliders -------- **Position** : Normalized position (0 -> 1) inside the recorded PV buffer. Buffer length is the same as the sound duration. **Transposition** : Pitch of the playback sound. Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/Transpose.rst000066400000000000000000000020551372272363700244060ustar00rootroot00000000000000Transpose : Phase Vocoder based two voices transposer ===================================================== Description ------------ This module transpose the frequency components of a phase vocoder analysis. Sliders -------- **Transpo 1** : Transposition factor of the first voice **Transpo 2** : Transposition factor of the second voice **Dry / Wet** : Mix between the original signal and the delayed signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/Vectral.rst000066400000000000000000000023251372272363700240300ustar00rootroot00000000000000Vectral : Phase Vocoder based vectral module (spectral gate and verb) ===================================================================== Description ------------ This module implements a spectral gate followed by a spectral reverb. Sliders -------- **Gate Threshold** : dB value at which the gate becomes active **Gate Attenuation** : Gain in dB of the gated signal **Time Factor** : Filter coefficient for decreasing bins **High Freq Damping** : High frequencies damping factor **Dry / Wet** : Mix between the original signal and the delayed signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **FFT Size** : Size, in samples, of the FFT **FFT Envelope** : Windowing shape of the FFT **FFT Overlaps** : Number of FFT overlaping analysis **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Spectral/index.rst000066400000000000000000000004761372272363700235440ustar00rootroot00000000000000Spectral : Spectral streaming processing modules ================================================ .. toctree:: :maxdepth: 1 AddResynth BinModulator BinWarper CrossSynth CrossSynth2 Morphing Shifter SpectralDelay SpectralFilter SpectralGate SpectralWarper Transpose Vectral cecilia5-5.4.1/doc-en/source/src/modules/Synthesis/000077500000000000000000000000001372272363700221105ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/modules/Synthesis/AdditiveSynth.rst000066400000000000000000000025431372272363700254250ustar00rootroot00000000000000AdditiveSynth : Additive synthesis module ========================================= Description ------------ An all featured additive synthesis module. Sliders -------- **Base Frequency** : Base pitch of the synthesis **Partials Spread** : Distance between partials **Partials Freq Rand Amp** : Amplitude of the jitter applied on the partials pitch **Partials Freq Rand Speed** : Frequency of the jitter applied on the partials pitch **Partials Amp Rand Amp** : Amplitude of the jitter applied on the partials amplitude **Partials Amp Rand Speed** : Frequency of the jitter applied on the partials amplitude **Amplitude Factor** : Spread of amplitude between partials **Chorus Depth** : Amplitude of the chorus **Chorus Feedback** : Amount of chorused signal fed back to the chorus **Chorus Dry / Wet** : Mix between the original synthesis and the chorused signal Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Num of Partials** : Number of partials present **Wave Shape** : Shape used for the synthesis **Custom Wave** : Define a custom wave shape by entering amplitude values cecilia5-5.4.1/doc-en/source/src/modules/Synthesis/Pulsar.rst000066400000000000000000000014471372272363700241160ustar00rootroot00000000000000Pulsar : Pulsar synthesis module ================================ Description ------------ This module implements the classic pulsar synthesis. Sliders -------- **Base Frequency** : Base pitch of the synthesis **Pulsar Width** : Amount of silence added to one period **Detune Factor** : Amount of jitter applied to the pitch **Detune Speed** : Speed of the jitter applied to the pitch Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Wave Shape** : Shape used for the synthesis **Custom Wave** : Define a custom wave shape by entering amplitude values **Window Type** : Pulsar envelope cecilia5-5.4.1/doc-en/source/src/modules/Synthesis/StochGrains.rst000066400000000000000000000051641372272363700250740ustar00rootroot00000000000000StochGrains : Stochastic granular synthesis with different instrument tone qualities ==================================================================================== Description ------------ This module implements a stochastic granular synthesis. Different synthesis engine are available and the user has control over the range of every generation parameters and envelopes. Sliders -------- **Pitch Offset** : Base transposition, in semitones, applied to every grain **Pitch Range** : Range, in semitone, over which grain pitches are chosen randomly **Speed Range** : Range, in second, over which grain start times are chosen randomly **Duration Range** : Range, in second, over which grain durations are chosen randomly **Brightness Range** : Range over which grain brightness factors (high frequency power) are chosen randomly **Detune Range** : Range over which grain detune factors (frequency deviation between voices) are chosen randomly **Intensity Range** : Range, in dB, over which grain amplitudes are chosen randomly **Pan Range** : Range over which grain spatial positions are chosen randomly **Density** : Density of active grains, expressed as percent of the total generated grains **Global Seed** : Root of stochatic generators. If 0, a new value is chosen randomly each time the performance starts. Otherwise, the same root is used every performance, making the generated sequences the same every time. Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Grain Envelope** : Amplitude envelope of each grain **Brightness Envelope** : Brightness (high frequency power) envelope of each grain Popups & Toggles ----------------- **Synth Type** : Choose between the different synthesis engines **Pitch Scaling** : Controls the possible values (as chords or scales) of the pitch generation **Pitch Algorithm** : Noise distribution used by the pitch generator **Speed Algorithm** : Noise distribution used by the speed generator **Duration Algorithm** : Noise distribution used by the duration generator **Intensity Algorithm** : Noise distribution used by the intensity generator **Max Num of Grains** : Regardless the speed generation and the duration of each grain, there will never be more overlapped grains than this value. The more CPU power you have, higher this value can be. cecilia5-5.4.1/doc-en/source/src/modules/Synthesis/StochGrains2.rst000066400000000000000000000044341372272363700251550ustar00rootroot00000000000000StochGrains2 : Stochastic granular synthesis based on a soundfile ================================================================= Description ------------ This module implements a stochastic granular synthesis where grains coe from a given soundfile. The user has control over the range of every generation parameters and envelopes. Sliders -------- **Pitch Offset** : Base transposition, in semitones, applied to every grain **Pitch Range** : Range, in semitone, over which grain transpositions are chosen randomly **Speed Range** : Range, in second, over which grain start times are chosen randomly **Duration Range** : Range, in second, over which grain durations are chosen randomly **Start Range** : Range, in seconds, over which grain starting poistions (in the file) are chosen randomly **Intensity Range** : Range, in dB, over which grain amplitudes are chosen randomly **Pan Range** : Range over which grain spatial positions are chosen randomly **Density** : Density of active grains, expressed as percent of the total generated grains **Global Seed** : Root of stochatic generators. If 0, a new value is chosen randomly each time the performance starts. Otherwise, the same root is used every performance, making the generated sequences the same every time. Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Grain Envelope** : Amplitude envelope of each grain Popups & Toggles ----------------- **Pitch Scaling** : Controls the possible values (as chords or scales) of the pitch generation **Pitch Algorithm** : Noise distribution used by the pitch generator **Speed Algorithm** : Noise distribution used by the speed generator **Duration Algorithm** : Noise distribution used by the duration generator **Intensity Algorithm** : Noise distribution used by the intensity generator **Max Num of Grains** : Regardless the speed generation and the duration of each grain, there will never be more overlapped grains than this value. The more CPU power you have, higher this value can be. cecilia5-5.4.1/doc-en/source/src/modules/Synthesis/index.rst000066400000000000000000000003101372272363700237430ustar00rootroot00000000000000Synthesis : Additive synthesis and particle generators ====================================================== .. toctree:: :maxdepth: 1 AdditiveSynth Pulsar StochGrains StochGrains2 cecilia5-5.4.1/doc-en/source/src/modules/Time/000077500000000000000000000000001372272363700210155ustar00rootroot00000000000000cecilia5-5.4.1/doc-en/source/src/modules/Time/4Delays.rst000066400000000000000000000040151372272363700230540ustar00rootroot000000000000004Delays : Two stereo delays with parallel or serial routing =========================================================== Description ------------ A classical module implementing a pair of stereo delays with user-defined delay time, feedback and gain. Routing of delays and filters can be defined with the popup menus. Sliders -------- **Delay 1 Left** : Delay one, left channel, delay time in seconds **Delay 1 Right** : Delay one, right channel, delay time in seconds **Delay 1 Feedback** : Amount of delayed signal fed back to the delay line input **Delay 1 Gain** : Gain of the delayed signal **Delay 2 Left** : Delay two, left channel, delay time in seconds **Delay 2 Right** : Delay two, right channel, delay time in seconds **Delay 2 Feedback** : Amount of delayed signal fed back to the delay line input **Delay 2 Gain** : Gain of the delayed signal **Jitter Amp** : Amplitude of the jitter applied on the delay times **Jitter Speed** : Speed of the jitter applied on the delay times **Filter Freq** : Cutoff or center frequency of the filter **Filter Q** : Q factor of the filter **Dry / Wet** : Mix between the original signal and the processed signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **Delay Routing** : Type of routing **Filter Type** : Type of filter **Filter Routing** : Specify if the filter is pre or post process **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Time/BeatMaker.rst000066400000000000000000000042571372272363700234120ustar00rootroot00000000000000BeatMaker : Algorithmic beat maker module ========================================= Description ------------ Four voices beat generator where sounds are extracted from a soundfile. Sliders -------- **Num of Taps** : Number of taps in a measure **Tempo** : Speed of taps in BPM (ref. to quarter note) **Beat 1 Tap Length** : Length of taps of the first beat, in seconds **Beat 1 Index** : Soundfile index of the first beat **Beat 1 Gain** : Gain of the first beat **Beat 1 Distribution** : Repartition of taps for the first beat (100% weak <--> 100% down) **Beat 2 Tap Length** : Length of taps of the second beat, in seconds **Beat 2 Index** : Soundfile index of the second beat **Beat 2 Gain** : Gain of the second beat **Beat 2 Distribution** : Repartition of taps for the second beat (100% weak <--> 100% down) **Beat 3 Tap Length** : Length of taps of the third beat, in seconds **Beat 3 Index** : Soundfile index of the third beat **Beat 3 Gain** : Gain of the third beat **Beat 3 Distribution** : Repartition of taps for the third beat (100% weak <--> 100% down) **Beat 4 Tap Length** : Length of taps of the fourth beat, in seconds **Beat 4 Index** : Soundfile index of the fourth beat **Beat 4 Gain** : Gain of the fourth beat **Beat 4 Distribution** : Repartition of taps for the fourth beat (100% weak <--> 100% down) **Global Seed** : Seed value for the algorithmic beats, using the same seed with the same distributions will yield the exact same beats. Graph Only ----------- **Beat 1 ADSR** : Envelope of taps for the first beat in breakpoint fashion **Beat 2 ADSR** : Envelope of taps for the second beat in breakpoint fashion **Beat 3 ADSR** : Envelope of taps for the third beat in breakpoint fashion **Beat 4 ADSR** : Envelope of taps for the fourth beat in breakpoint fashion **Overall Amplitude** : The amplitude curve applied on the total duration of the performance cecilia5-5.4.1/doc-en/source/src/modules/Time/DelayMod.rst000066400000000000000000000033111372272363700232430ustar00rootroot00000000000000DelayMod : Stereo delay module with LFO on delay times ====================================================== Description ------------ A stereo delay whose delay times are modulated with LFO of different shapes. Sliders -------- **Delay Time L** : Delay time of the left channel delay **Delay Time R** : Delay time of the right channel delay **LFO Depth L** : Amplitude of the LFO applied on left channel delay time **LFO Depth R** : Amplitude of the LFO applied on right channel delay time **LFO Freq L** : Frequency of the LFO applied on left channel delay time **LFO Freq R** : Frequency of the LFO applied on right channel delay time **Gain Delay L** : Amplitude of the left channel delay **Gain Delay R** : Amplitude of the right channel delay **Feedback** : Amount of delayed signal fed back in the delay chain **LFO Sharpness** : Sharper waveform results in more harmonics in the LFO spectrum. **Dry / Wet** : Mix between the original signal and the delayed signals Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance Popups & Toggles ----------------- **LFO Waveform L** : Shape of the LFO waveform applied on left channel delay **LFO Waveform R** : Shape of the LFO waveform applied on right channel delay **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Time/Granulator.rst000066400000000000000000000025441372272363700236720ustar00rootroot00000000000000Granulator : Granulation module =============================== Description ------------ A classic granulation module. Useful to stretch a sound without changing the pitch or to transposition without changing the duration. Sliders -------- **Transpose** : Base pitch of the grains **Grain Position** : Soundfile index **Position Random** : Jitter applied on the soundfile index **Pitch Random** : Jitter applied on the pitch of the grains using the discreet transpo list **Grain Duration** : Length of the grains **Num of Grains** : Number of overlapping grains Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Grain Envelope** : Emplitude envelope of the grains Popups & Toggles ----------------- **Filter Type** : Type of the post filter **Discreet Transpo** : List of pitch ratios **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Time/Particle.rst000066400000000000000000000034041372272363700233130ustar00rootroot00000000000000Particle : A full-featured granulation module ============================================= Description ----------- This module offers more controls than the classic granulation module. Useful to generate clouds, to stretch a sound without changing the pitch or to transpose without changing the duration. `Density per Second * Grain Duration` defines the overall overlaps. Sliders ------- **Density per Second** : How many grains to play per second **Transpose** : Overall transposition, in cents, of the grains **Grain Position** : Soundfile index **Grain Duration** : Duration of each grain, in seconds **Start Time Deviation** : Maximum deviation of the starting time of the grain **Panning Random** : Random added to the panning of each grain **Density Random** : Jitter applied to the density per second **Pitch Random** : Jitter applied on the pitch of the grains **Position Random** : Jitter applied on the soundfile index **Duration Random** : Jitter applied to the grain duration Graph Only ---------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Grain Envelope** : Emplitude envelope of the grains Popups & Toggles ---------------- **Discreet Transpo** : List of pitch ratios **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Time/Pelletizer.rst000066400000000000000000000033361372272363700236730ustar00rootroot00000000000000Pelletizer : Another granulation module ======================================= Description ------------ A granulation module where the number of grains (density) and the grain duration can be set independently. Useful to stretch a sound without changing the pitch or to transposition without changing the duration. Sliders -------- **Transpose** : Base pitch of the grains **Density of grains** : Number of grains per second **Grain Position** : Grain start position in the position **Grain Duration** : Duration of the grain in seconds **Pitch Random** : Jitter applied on the pitch of the grains **Density Random** : Jitter applied on the density **Position Random** : Jitter applied on the grain start position **Duration Random** : Jitter applied on the duration of the grain **Filter Freq** : Cutoff or center frequency of the filter (post-processing) **Filter Q** : Q of the filter Graph Only ----------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Grain Envelope** : Emplitude envelope of the grains Popups & Toggles ----------------- **Filter Type** : Type of the post filter **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Discreet Transpo** : List of pitch ratios **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Time/UltimateGrainer.rst000066400000000000000000000046531372272363700246530ustar00rootroot00000000000000UltimateGrainer : A state-of-the-art granulation processing module ================================================================== Description ----------- This module offers more controls than the classic granulation module. Useful to generate clouds, to stretch a sound without changing the pitch or to transpose without changing the duration. It features a grain filter with control over type, frequency and Q for each grain. `Density per Second * Grain Duration` defines the overall overlaps. Sliders ------- **Density per Second** : How many grains to play per second **Transpose** : Overall transposition, in cents, of the grains **Grain Position** : Soundfile index **Grain Duration** : Duration of each grain, in seconds **Start Time Deviation** : Maximum deviation of the starting time of the grain **Filter Freq** : Filter cutoff or center frequency, chosen at the beginning of the grain **Filter Q** : Filter Q, chosen at the beginning of the grain **Panning Random** : Random added to the panning of each grain **Density Random** : Jitter applied to the density per second **Pitch Random** : Jitter applied on the pitch of the grains **Position Random** : Jitter applied on the soundfile index **Duration Random** : Jitter applied to the grain duration **Filter Freq Random** : Jitter applied to the grain's filter cutoff or center frequency **Filter Q Random** : Jitter applied to the grain's filter Q Graph Only ---------- **Overall Amplitude** : The amplitude curve applied on the total duration of the performance **Grain Envelope** : Emplitude envelope of the grains Popups & Toggles ---------------- **Balance** : Compression mode. Off, balanced with a fixed signal or balanced with the input source. **Filter Type** : Type of the grain's filter (Lowpass, Highpass, Bandpass, Bandstop, Allpass) **Filter Freq Ratio** : List ratios used to randomize grain's filter frequency **Discreet Transpo** : List of pitch ratios **Polyphony Voices** : Number of voices played simultaneously (polyphony), only available at initialization time **Polyphony Spread** : Pitch variation between voices (chorus), only available at initialization time cecilia5-5.4.1/doc-en/source/src/modules/Time/index.rst000066400000000000000000000004051372272363700226550ustar00rootroot00000000000000Time : Granulation based time-stretching and delay related modules ================================================================== .. toctree:: :maxdepth: 1 4Delays BeatMaker DelayMod Granulator Pelletizer Particle UltimateGrainer cecilia5-5.4.1/doc-en/source/src/modules/index.rst000066400000000000000000000004731372272363700217640ustar00rootroot00000000000000Documentation of Built-In Modules ===================================== The built-in modules are classified into different categories: .. toctree:: :maxdepth: 2 Dynamics/index Filters/index Multiband/index Pitch/index Resonators&Verbs/index Spectral/index Synthesis/index Time/index cecilia5-5.4.1/images/000077500000000000000000000000001372272363700144605ustar00rootroot00000000000000cecilia5-5.4.1/images/Cecilia_about_small.png000066400000000000000000000120071372272363700211010ustar00rootroot00000000000000PNG  IHDRFFq.sBIT|d pHYs B4IDATx\wp\ykA ثl2)[9"1%-8nL&{&ub;VK=8ntHˍdbQa@;\nx@|3{_o!t#IP>o,ɤӫ&%B@4i^[!?PGGr{zzv D'5˓B;xU(`4^nmmyPVNBU饗64m HJ! ,ped,MkWw{w5D#_$ ąs|.#_~U;dKK`m1 >uyw:8Ux @X<\" l^>ֻҲ文X f O ]Z(B qss'}> w!眷~gzB@@A@!hw[o'W)0T'O_oֶ@bLD(w8gϞ3&]){ !C<1"@uH1vM >և55ȇ?-k2W\> Ru@)&B&'VG>P(ے-yw?I$e6""59OpURޣ2O}sjERU P7H7=Տ?d2;Ky$_y{Tk}Q0!G#_NfDSDE+9h}?~,+]7}~>&|=QZB@)i I!D*=\.imP TE~17Bs4!>ڋ]V\ΗDV߿鮁duf)(mTNɤ02ڇ+%Ɛ32pA/Jq7t%C0*AUxjTU!#R6<w]=oxzy|kG%y-,6613F.dYdJf J %cp;6ہp8z^T7(9myD"տ9%0r>`YĘd2s(tOTrsp]"諂OBpaH'đ H &)B`]P!ȃ%"H||pT{ ˊU jW<{m@1J!cۈj|jT5@U< Ie08ڃ=i(VP; "+AZ鮶ʕ?U0{AW~ ݧ  $0wqv6.Y I.8KX|#6Թ2)Qve!S W?GW|1ilq ;t跟*eR>B2S>h>/$Eۆ&i}[e(pWDZx}]hk\|& #Xݏv鿻 $wxյPm2;+ ] @h4@e* 52  @]1=?Ӫ) / 8q|_Ƥ #zY+ؓBMppMnGUJ"qcـț7z3To(x(5&}}Y -hjlrIC:!qph0@K۱wpH(e 686ɤqNtKw. ('o:Tsbh4 T.iJ) L&' ʬb,# '9oXl ϟ8V 7&'6,lM 'p!9Ȓ%`mYc*r(J"k#mcÈ&GB)fyӀWxt/&Q=T1G,=ekP B LD:,v:\"›6ƚ[ *N۱뢪 (_8>$GH, @<1eMvPU6.R^D/y"V>`IeHbt{qI}M4od0M##öLߨ'Pa$:^p HS׭IHeY ZbeYձM@,kZ۴i$SLN,0\}L@իVp _vOejRS9Ga[֬nP }]X^A`(ڋiT2 4TeͿ6-4C^/-`scK(F2[x]cF"/,)(l*5+O0&L]n$sB͉s7 JL' ݧX !GTw Bk1\H<'m 6~'9i;u;!lDznw ;BIxb( >=Z؄dSqlX6ش`|߲chI8hhh0sƬP@e '_=Ȃ^=c *۾ع;0Cf[عs4nYR[ml5Ia G_5&b*GdDLJq3i #e!d :W|;v]w$9?l$ aXݶV64gx*B)UC?G@W!KߍSJn<{x?"FGUVW\@c޷gBHsٶH߄h}Tꕞs8s8,PT V>'ocI]+VoB8\F%+Scq Wp<\%B`[jqJB)cؽ}͉{שMއG]YNJ{1 Qt^|C ,+]U4r*PH#Bjw;7HN`,:hlH*s܁kJ#eV2̧^﫳>7C>]UVRJ!X?z;1L3B &f uB_*1Ȋ&jͅ$*!M*ZI#@jx+>;s6@ [{_42XQf(9&Rل[!+*Q #AmU#"Z@5"x;wڵk&NKj77~<0)mBm[dHHHg峰tl;{ MfQ珝   |mYHG{ݻkǎ;> 6Б~s -y7,UŴAP7m0Ȧ)߱u]wkwdQx |sxe-$Ʌ`Vr㮁,nϤΟ?3;t"8Q ):$Yd_*ͣۂm9T&7[۴qÓ+V>B*S4{dm2U2YrB]ȌB(Nkk"͇Z[W"!tO5?;\.9PaȲW5mg5/Rtpc|fCIENDB`cecilia5-5.4.1/images/Grapher_background.png000066400000000000000000000424251372272363700207640ustar00rootroot00000000000000PNG  IHDR x sBITO pHYs B4 IDATxݡ0EkP" xvу⚹sfyYkiZkZkZk{3 \{_khZkZkZk~ۙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ_3 L6SZkZkZk:ڙ{wP!̿k }a`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkR{ IDATf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 @A 01%;TZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZke}L8IDAT71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0ljZkZkZk71`L6SZkZkZkzڛ_ 0l ZkZkZkZk=/pL6STZkZkZk`8 fZkZkZkZO{ 0STa3UkZkZkZkf*ZkZkZk Ta3`LZkZkZkZio~c* 0=!PqIENDB`cecilia5-5.4.1/images/Mario1.png000066400000000000000000000002741372272363700163210ustar00rootroot00000000000000PNG  IHDR(-SsBITO PLTE8|@PtRNS@fOIDAT][1EZ|d?. Jx@:mr["]b'HA3̪CSy1}ٳ;6;)xIENDB`cecilia5-5.4.1/images/Mario2.png000066400000000000000000000002661372272363700163230ustar00rootroot00000000000000PNG  IHDR n5sBITO PLTE8|@PtRNS@fIIDATUN!Bj@0j!ل}#V$.# uͳ˺**:'3CYHjyz>wۮwIENDB`cecilia5-5.4.1/images/Mario3.png000066400000000000000000000002621372272363700163200ustar00rootroot00000000000000PNG  IHDRFsBITO PLTE8|@PtRNS@fEIDATe ܰ)ڴHb;j331M=kf7\>q+ IENDB`cecilia5-5.4.1/images/Mario4.png000066400000000000000000000002761372272363700163260ustar00rootroot00000000000000PNG  IHDR(-SsBITO PLTE8@| KtRNS@fQIDATe[! >:L 0A d9O#e$XHgOGD-G^LC[pE~i7Ie@ܴIENDB`cecilia5-5.4.1/images/Mario5.png000066400000000000000000000002661372272363700163260ustar00rootroot00000000000000PNG  IHDR n5sBITO PLTE8@| KtRNS@fIIDATE1'ɒvS'sTNPr RWė0SFjl/dydy-qIENDB`cecilia5-5.4.1/images/Mario6.png000066400000000000000000000002651372272363700163260ustar00rootroot00000000000000PNG  IHDRFsBITO PLTE8@| KtRNS@fHIDATm @!K˵hݯ"\aiv(*>xMTS{"_873&(ҙIENDB`cecilia5-5.4.1/images/arrow_down.png000066400000000000000000000004701372272363700173500ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs GIDAT]jP)@_${.Щ8u@I(4+oy޳i(^Hu7$o58~*4 Çb-0ǹ ,>yA_t$@J'@UU8s8M{I$I4}W^W_MlnSM(u]m۾VjQ`ImYZ?(l5IIENDB`cecilia5-5.4.1/images/arrow_down_hover.png000066400000000000000000000003531372272363700205530ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs GIDATmбjBQo}X _R"ZBT)5X\9he +|'UU0p¾vj3XH[?xhZø.$@^;wK, Q>%IU73t4g.[tMIENDB`cecilia5-5.4.1/images/arrow_up.png000066400000000000000000000004671372272363700170330ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs GIDATen@g {Ȋr) x Pߤ^W_ ,") NM\zL#Hb#IGZAc@4Ms+IR$1e6rg40PJyk a<ϳO#5mu}" g,SWu}ߟqt8β]F)TU&EQI~Y.IENDB`cecilia5-5.4.1/images/arrow_up_hover.png000066400000000000000000000003411372272363700202250ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs GIDATu͡A` M33KEhŽƿ;Lnmvh?D zW M±!3 #L#bVIf*/D1q>tIENDB`cecilia5-5.4.1/images/audio-click-trans.png000066400000000000000000000010541372272363700204770ustar00rootroot00000000000000PNG  IHDR(^\sBIT|d pHYs B4IDATX=hAKU $ڈ $B F,6VۨXDlP; wXD~ķy7sp;Y3a`X5)VbcӔP 7f<)r:GSqeZQq2x0G;+8WplHQ7S؉)ľ*5j0؍ E))/r(wѬA!av`* `2G|m8Z>sRc bV}-뱦!`vo֛)գzlqEU;Nl 9ᶹ4z؄ظoGQR'1-84Uٚ~Cx*'WB(n-%vۉVHBq;Acǭo^4i0;Q7Ub` Er)IENDB`cecilia5-5.4.1/images/audio-hover-trans.png000066400000000000000000000010611372272363700205330ustar00rootroot00000000000000PNG  IHDR(^\sBIT|d pHYs B4IDATX=hA%Q*bD$ڨ Q?XX(vCPAA+*Q`5bڤV#&yBmP;00spؽ{gtkC`l+`QSP7&<b:GCLp yYU8xb:`i8g*!IS_U[CLݸ'Xb*lVIp5a_aS9w9P3lfs?',}LSsߝC1h`-V539>7CLO7qUYmzbK1 ̥уFx۫b:Q\1׮ZB?tW*ƼK+Ve1֪vۿSqg.cgw5k$!^y잟`;m}K\.nt vCvѼIENDB`cecilia5-5.4.1/images/audio-normal-trans.png000066400000000000000000000007771372272363700207150ustar00rootroot00000000000000PNG  IHDR(^\sBIT|d pHYs B4IDATX?kTAD FF4*ATYFDXA,Ma&llM"kquޠ}`sÙsJQ })9Z]m <8c8VbIh,f;[gO 7}8Mİ4/Rūf|d6T˸UpVc14sԲxnTpn B$Nrw@[My~~^UUEu?3vkzE|h'/_ԭ28*c39.t1IENDB`cecilia5-5.4.1/images/cecilia-click-trans.png000066400000000000000000000026511372272363700207730ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4KIDATHKl\W߹d<6~uGiSB! h@ EUUTMUɨ`H ⡲@!JMFE nc?&9,<68e|1{aP%/h"hjT2+O=sfqtt.7ffعB|i_n5N@2bqOaܹD׶"cQl0.Bǣ}/N |7^~С-ժ[!~&e.zyȑgoO8rm b Ȓ A,  ϛW^%/]d߾<, Z8Try"EDpƮVpgg̗Lz'NAZ3לgx<5@:7.#"(ʕ Wl\.߱^: h\x-';2Lfb8ّsE4¯9 :GFF wn O8Q |.R*oiYB} ܭ3fԩgkUpE}=o1#6:f"gF) q,qc&Ɔ`˪B4=-g4-dp,E43KhڨycYXݸѻ*rfs\-{dI\K"nb 8K@ܼ[n j J@)Z,a&CH,Һmn}Upkkns3q7?珏,fv='ZxCر釾s$- >VJ% iކ50;Y뭷zoi!lL%&v]L*Ec:4޽WNKkԂ`_ƭ $^4D!`\d(2Lۇ bmy;k}_Yk%&m vnQ렭3 J%LTS]q9w|⺱c/Z\Qͻ;^"+B" XkPrgC_˖lPI'p:ݻ_|z[\_m_q},BZk0)`-Zrz_cJ lz떾)ca3 QWP]z ske6X,v&Wb箽[SWJP!$HurYPD#3GГLwR{>+r=[1)y~THnGw̪KPdp#ϏgѶU5?3!#cYǫ,"&c"PR1B[ rTjd'O/Rr.$kQxNMXõ vaAU+7 Z뚾L=c BHTp㸔U"*eDJI Z5rBH3 atZŐRJV(pU c KKiV`d{d.n!Q[W$.*K!XˈbQa4%Q;dSU_-Z+ Z5$G5ٱ~T⧔CFUж`}<|qb4&ZQZu縞BGaw = W2,̶4'L ,H^b1k e~-u ԹCN~ l2:|O ޏv|,!満-TQۅe35xd'R&u9/wC88! C`C47/<9oMy0&S;L.Ɏu:T`-fT,-YMC:ntg 3߲~ Dz3GIENDB`cecilia5-5.4.1/images/cecilia-normal-trans.png000066400000000000000000000023401372272363700211710ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4IDATHVOlUffwfwlniR"߄ThE ) 1՘x\0¡1jDUDIŴUV%-).]۝78ݶK~;޼{nW\Icj\nGr٧y^q8ciYuUVVӟKea{|Eǁ:H&SE돊xeRԁH$'I*&TUp8R6M&նH$Qd&1Ɩa࠮j[^Q4mk4*nT `SS8kz0aU!47nA˶jjj$ITsWUV\RRb_|wcIlن֦f/R/Zn'hfor3AY_SmV 6Qd=:r bڵkdXU`0hGFo pǟmG2ӉG`r$v`Ri)ΦEq 鑺z90l"P1cl 8VDD|% $I iN—?t0tycW06A:Ctyx=rK^l$QZ[ <9w=c5t3v:pLf` yaV`] `̲,,kMle˓`rAϻT ?4jM D"awZ躎[¹ۃ3:4 O\cp8/W2m?L]|h?}{~ V+notAevAAG< +ΪjAS/YдPoX˶@!Sݦzim2'Y ٤t 3Ю5IENDB`cecilia5-5.4.1/images/delete-hover-trans.png000066400000000000000000000004651372272363700207030ustar00rootroot00000000000000PNG  IHDRVΎWsBIT|d pHYs B4IDAT8ݓ 0?}t.йK 2G`sI6H.#YH@)Nw:,/q"`>+btnBV+g!_[R $"2crdԑ*ou<":HB)㲯@܃: Qg[hs$[}8*w^7#m4'{Q&{4ዛiv4mݡUzRA!H+:5Tr UPf F 6ݏ~73jx7|ՏIENDB`cecilia5-5.4.1/images/edit-hover-trans.png000066400000000000000000000006501372272363700203620ustar00rootroot00000000000000PNG  IHDRoUtsBIT|d pHYs B4JIDAT8ӿKaY`nB AH6 7!%D!4$T?K8Ԡ$3CA-m qrxzj˲kF`כY^t`0bجfyqN1촪lGxY^ W: jī`Ї&4c _1߲MiƁFS&SWjTim:I?m@ F 8^*%TwU]Nxk5IHITmu(G-e^C )u|0>o~߅ 'P˛IENDB`cecilia5-5.4.1/images/filer-click-trans.png000066400000000000000000000025161372272363700205030ustar00rootroot00000000000000PNG  IHDR(^\sBIT|d pHYs B4IDATX͘khW3;;Il6dc*!KY+BR-4a%S DDk`KCA*^j&RSmM͚޳ٝ9T%ɚ p曇=B)ų,iL&V b<LyYQ`2`J P ݝuС77oVj(~ B0"|t݆ _?? Iu ᧱WO4z$ 7ߗ)ŬW[}o|bgccESL9f{z^v^*QAQB),n7" ?7{ kܶg+*+ RW^ys|]M 4 ߹orqBdrS\PG)be )W0VKvz=222{袊!\[$H,4On/=w֦d3, hT?,WE WQ9͎u#>0uqW]]űtPdɀtilttY+6|laA\I8o?[[g 3\USs+ >WKwÌFwttov %!W@E,9Cy tݎyՐ%) qe. >YIEQ4ń%kzR ?pLp , <&dRAB(e4zHS@ TRZ @Br ˢ]| ML T q΃:!2Dž;;;Z-0Pd~9rZY.d!79907ȷZU)V,\x奪׎67_}|& Bd@qBW#jS>۶ukA,I@T.3g묨P(ԛjUOCg$ mCIENDB`cecilia5-5.4.1/images/filer-hover-trans.png000066400000000000000000000024341372272363700205400ustar00rootroot00000000000000PNG  IHDR(^\sBIT|d pHYs B4IDATX͘klUcg.m>JJTB@FEBE!&F%>1!D5V@[h)`[-t}u;Ф;ۢ0s=!sʢ7h(!#Z%@0_,kx0F`#Օ]X89Yuc$`k|Z:<' ƚU]'$.= 6q&KBB^^|9_9o.ORdaeQ@Ck3n}c GATxqs`KpDw;V?+]T\H GNހ ;W:-K⚵b86Y!ⰇRd 'fDX[wߴkob&#a濯 H9+&wrjUК۲ZaT*1LӒ1Nmijhƕ*~861^2ZD/b_my X?88\/f?1ʆ'mk  V!Y9f,-%E-൉)Dn'N% CMBÍ0Y,d|KLå>\ i]h083'E$ Pl2cH4Y$$ kt:$f4ەLNJXV9_$HPD~_[0AycW@PtsmȎ)Mt˂شF4{Й5l_pp~|sf7 *NX^^cXoE^;w^@ñ޽{U}q;,8޺ï݃sss_M&v\=0 3Ye(...ۿMWW͛7oY,7  ޿&//n!N 3EJ)E񓊊6Xbmll;rH`K1:Cccc z3F)hZ!v>sĉ_:::NRJ۫D>c F "t: ,`Œ`G/\%l3! DEǃ$IZ߳ھp}}7n8)IX?rwc4L%MMMgٳZ]]STT pdd$n ---uK aMLL:N:11DB 2KB$Bm6ۊMeYNhn!ms899 D笵Q©!Rp\p8ѣGcǎ孮~zա|HJJyPU5a "eYsHi {999IgϞ/l9YYU$I NMM͚昕@~855EB!,RU5ÇN'|³gVIZZ& H)UU,z-ׯ_g322*~uOOܹs_d*"}A*!dgFN>X,ٹۻxҥkkkF0ѐ^v-ܷ@UU`z=(~_aJ܀jl6=33R&gqRwog&JB DG\np^_-/=`D[ , xI\&]z\L!cU\;.Lv~<<}U&3Ηhpd̲iL&&2Ԉ]JD{ lyyXUɸ/-m-c2 0n fEv%<*eB&M5OpN ؛9IENDB`cecilia5-5.4.1/images/hand-hover-trans.png000066400000000000000000000010051372272363700203420ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4IDAT8Ŕ1HA )I-vCBR:60 ̾NĔʹ)*gErb4!$ŝh l G8$a罙WIӔ^SnhˈvJ%e#:  0wS0,hx#a cK-;wv;BtwňzgqG97|̞mѥl6`?h5l^Z}mD%`6$=CH;:Җ*\NOFD÷=Y&̈6߀`8RlDk@3M +v'/|ފd+N.R7Ft<Ǘ@Z;v"%#8 =`hTؼ^qFQ|@ӈ@8ɝ>_uw eIENDB`cecilia5-5.4.1/images/hand-normal-trans.png000066400000000000000000000006361372272363700205200ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4@IDAT8Ŕ+DalRBl$* e%vD6JX|e%%Y`cNfДSs{vONo*@gZ.aQ@/`X8QR :^:էd0zZw-m>Rj3I&\TwBUmQmؑKz~GBӀՇC0A:Jp#WUSrE]U}O]qY@H-Z=m*^VDTjwqzkXM޸mg۰HҎtIENDB`cecilia5-5.4.1/images/hide-hover-trans.png000066400000000000000000000005211372272363700203430ustar00rootroot00000000000000PNG  IHDRVΎWsBIT|d pHYs B4IDAT8?JCAOKv >L~ \ 0ORXI:c=_}8vflgP7-&.XC$(ˋx8p)f)+S1H1SoTu_j*cC/ˋ6Y^q6z)磠6A 7bUJ-LUa;m~X߳IENDB`cecilia5-5.4.1/images/hide-normal-trans.png000066400000000000000000000004501372272363700205110ustar00rootroot00000000000000PNG  IHDRVΎWsBIT|d pHYs B4IDAT8ݓ1@E(mx ZNZYZYsl/l 'ȟdvȞ)4n!.@X*ryf>J{2}C^%9ԑabwo/q  #C\'. gyP3Y-j1yŸ lPWV 8g=[!޿6Y'O">iGIENDB`cecilia5-5.4.1/images/input-1-file.png000066400000000000000000000007331372272363700174030ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4}IDAT8ԽjTQoτh,cR B[B,V"X6B| 14 :Yw1W'psϺ{s#_;$[!ܯaV i|&>$\UWU~Kržd1ɫ$W{->6ZC pIn$­$Jr䗼$W$YMc%IYZUmvzOrSw9⇦ϭh# N!5</[%Ej@G$oVճ$1uvN|MaU=h0vɄGgIENDB`cecilia5-5.4.1/images/input-2-live.png000066400000000000000000000007771372272363700174340ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4IDAT81oAߚh@H.(\/R 'H(,PHnE\ |'|xoovf"X8mh(x̼OouYk,js) MӼbu]s":o1Fm亦R6Dcnv~/LSL&ngUUl2a۽ q3~Uѽ~t^--@Uo?u}BDgK+#QU}r#z4ctI|3ƼQ8[񼪪ON2!c}ˢ(91eYv'e\Nyg"Rx3kWt!1;q!c"zK7 MYoy_Wթh^oa}B""̜yGϵ/b8wc12QIENDB`cecilia5-5.4.1/images/input-3-mic.png000066400000000000000000000010641372272363700172340ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4IDAT8퓱SAzFE,LXX ZZv!lP$B V !X½L̙cad͊7g _Mq״֟,ˊ0LDd)k훼/"vz1fwtWDH\`VgwD #.lz=vtʹ&Fղw<"T*RZDf.-1vZzL;N BYDxGA0 oEQ8k*k+9ZU)9(D􎙿 bxn}19!IƘKͲ3sc"zFDO3wϚt:']17!3sιGZ뛫dl u@$4MZ{0L4-@|^'c{?&ccLܠZ%%"l??Pg-M3J?IENDB`cecilia5-5.4.1/images/input-4-mic-recirc.png000066400000000000000000000020161372272363700205000ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4IDAT8uTMh\U^'V脩&FH\XF,`E#(RwqJVAG㢛.܈6f0I&d Ly3o{w{\t^i=|rŮ566f54>>~Dk_h6GDDzdd2i*933S liq~R`fc 3=ϻQmmm ՅW0,R mh4pZ^Z/cnPdaa-!64 s x4滮1ƖRT*}~l:۶/-- I)OH)?R"bRfmbPd$Li7o|+c%3j?cj~͈0<*.2!15c sI|\kZ'}L)6MD8NO[3na!L6:2g5!ė~Zi^}(ލ9j'݇̔H$:-˲cLzzz.2"O$˲>j b]&!JӝF9: y7J_|fZEoƜݱ^]ٲesx|q0  X]ٴitD"eYXER\.?r;ެf=PtWzIc1>L&@X󌌌`YR)jkkQrLTX,Fill$Lba`6]]]wvv~`!0.J) D?.===twwH$ؽ{7uuuDQTU%*iBƸr b6ibkkkOĐB%GfGyihh@e`a ΝreYK>ɓ'֥ӫ'5Mò,Ξ=ٽ{7l/_ڵCOMMM5v4Kݻm 뺾u].]D> 6,l۶m 4㠪jӁځwÀ.^RDOO۷o'Hp566o>ƈD"XECCS)`/*,ξg}v\pX,F:k8S|q 9 *A9)%i2>>N6EQ/D"155۶ٵkwXzGVAP;mۦ۶ٸqg2lquĪ *+kbbx<"v*L]בR8,O<@T7ꖖme-˪, UU)Jbu󭡡jJ躾1Tgff*`"S*OJ mUU`*bjj ۶4_lڴ>qB𳙙xWnFm]]۞˚τ~&EQΜ9sB k ttt=AӴrb.;sV; JNNNjUAřL0p]wLdffbiaW*R177\Zre111Q` sYJW(ӗ^z):::ai&''UE49^BR?dr )JR@*T*i `zzz/_.]뙱4A t?CCC 4Mqw 7<cL$[m +E8,x qE!sM HL&I&(2 2ix?('BV . ,l6Rǎb|0- 0 Ì@4SS>0Artvv>r-b !Ģ kjoo.RJ^ gG&l=a-X=g"`anӬm AR'NdE VRR%tx,"^:ٳsv]tՙpg8|9-Y/޵m+ E`3<8R2:z+ی0K[6Hק6qi#4abf϶mv AbOO?zBy715Ŗ˯c#E :;;^6A5oܹE)ïfbkl¶`TUt]w֭[~ %栙Z>/G.d}IKǧ/͠FJɭuїUL!F)%oZ+0U x8 G1 ⡡KCCCoSu<XYd1QYIī%\Tϛga~Ӏ5O+555uRц(g vg ՂL/Sf>hftt72R!]80l2J);VߡZt)R *̶JB׍C9S뙱هﳘ4+R~F5ݹ0224mAzB=?7T<ɥ)H5/{I.`\={P*F<ϣ͐GV\(8sлH)ƆbWx+*̙}QmK>#˓H>AI)G{ Gj$u& tj;{ݏ&P^C6Z+MRa1_zwq]7%&`׆ =sya֠0!`.EA(`ZJ:9̥8AP(|ttua`j}hJ&!mպ۷ua=ADF+@Js/\.#*ŋ:uP3RpiS*ƐM|g=== !z6GH?OhQJMLLx?G@gNiǵ>b%V[&rgضn58J7FO87<<Jb1x)%ʭH:0$ZNv7^5KV}|ѲZ'?$nxG74=i*O/p-(l`ƪ~%!PIyP - vՀ HRIX]ƖIENDB`cecilia5-5.4.1/images/load-hover-trans.png000066400000000000000000000004511372272363700203530ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4IDAT8픱 0H'IgWJ_@t.X<Ԧ *xӟNFAvLw7b΀90~R蓏)0btbn]1GS 6IJϬUb'*{p]rW/J5JAlIENDB`cecilia5-5.4.1/images/load-normal-trans.png000066400000000000000000000002711372272363700205200ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4[IDAT8c?5UM5?A9 q*B2"%r# !xV X!2E$`7,39{A%IENDB`cecilia5-5.4.1/images/midi-click-trans.png000066400000000000000000000012511372272363700203170ustar00rootroot00000000000000PNG  IHDR(^\sBIT|d pHYs B4KIDATXKAEHHHU%͙?DD 4r#&4 p2-jZծM $ H0-޹p15qa0jR ZUQىG#@7)RoЉ>#s`8UN6W.rM^MJU2jJzWJ5?`,Z%byOrn2o`ҹzL$,o>7 |x,`8ʉV"p 0 xuKi]~DtO ,PZ/œ{sjs)n=uʦ#0` Le&&0Eui*,0DoII'z~ yY 'NZ$N;'Z:\;\RDPs&u3ux8tQ4;E\ғטUl 68ctUbc,,{<=18kX*%:m/Z?84MXU/rO/^EIENDB`cecilia5-5.4.1/images/midi-hover-trans.png000066400000000000000000000012411372272363700203540ustar00rootroot00000000000000PNG  IHDR(^\sBIT|d pHYs B4CIDATXkUGODh`u%JED#MPKB7ZAnɢ.4tc.[PlД"H ŝ#Ys8_9̙Y+Ykoi[v8WuQg!c40M%g>#x؀_ᯪ[;XֽUR 6k5R / `Uc)?:?ƕaS m[%P8;@M=O15,wR[}%CU.R p]N)8] t:W`َ#p &4S\,-\6ŖR4}CԚN{_pR 0ayL1L1<ưf:P7:?[u[[,7؏q UlǭÅ c#F.eь);okuᇞ)6LUÚ,YY5 [.)H7C esYc3B>{C+˿ حVՊ| oFIENDB`cecilia5-5.4.1/images/midi-normal-trans.png000066400000000000000000000011261372272363700205230ustar00rootroot00000000000000PNG  IHDR(^\sBIT|d pHYs B4IDATX=kAhM!&K+A"& ؄UZƸbb!*bDh5 h# ˲fvX{3wTel\"W` s0Y8s08,j'6.֘ӯ^RJq:xAjV6?KGuڰﲺ9X>[).x#Vk-xjӐ`\[ȫ ~R>~3[(޷3/1`39 #S+k E*KӶ|AZ7>Ʒ_~Wo<0 l4w xX*JS5F6yq'n]-ՑY:ע땤fzP98p|8 .: #`E:V/ªלu'0<)sIENDB`cecilia5-5.4.1/images/next_24.png000066400000000000000000000032101372272363700164450ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B4*IDATHW?.3.\uAmBC[5[_0m%5^C7ڦZ&-""iJJؖ , 2/s/F6>y|yy<7ґ}b+DgyNY̹csS)~.~F'Wc(Jk0ke " r-hAhHV ^6"bշ1@JyϷV m M^a~`_YEtCq:;⢄jA,CI|wkW< th/}|VƦgz$6,iqWGTDy ŋx0s|z0p5 j*h&*Lԧȗ&ړg׃+ցk<`:x2ZPW5& Yɕ3vNS6 tTMHKRWuڂxv w mP4$.0i_ 8:vbM*^hd$A[(NG$CTvx)ӄ\H#hȀL lB}s{ OT.32yE n*oX"hoڇ@<A)E4Q,T+h]T)v ӧ!f9[Iu#-c(/?AXv|>O#DP$}1;F!jEr,K ҍ$6nJCJɎ]<"] 8ACC5HFLFPw+H` pl7D($ Oma%k_kA'Eya:i,!R]TJTR H K PkmvϾB /F-%M-dhe04r_Z*uH-- v1BS-IΛӻHh E=0@Lz=?Vz:tgіBp-P8X,Nz{n>> [^XaCk#IƱ,|Ў ?pMz;od'ѝ-'%zuء4UNduqD$P3N|rwIENDB`cecilia5-5.4.1/images/open-hover-trans.png000066400000000000000000000003461372272363700204000ustar00rootroot00000000000000PNG  IHDRɪ|sBIT|d pHYs B4IDAT8c?%"ݣ`^ÁRԫ츯^#\C ""|a_`~|PIa< h 4cs$nNHy (9c"!08+'xdwIENDB`cecilia5-5.4.1/images/open-normal-trans.png000066400000000000000000000003441372272363700205430ustar00rootroot00000000000000PNG  IHDRɪ|sBIT|d pHYs B4IDAT8 0Db!tؑ؁V%xQ57q`IBIQ* iV(ɛCM$w8A㣝4B ktG;Bo Py`g@ wX 7X| IENDB`cecilia5-5.4.1/images/path-click-trans.png000066400000000000000000000006011372272363700203270ustar00rootroot00000000000000PNG  IHDR(sBIT|d pHYs B4#IDATX1JA5jLck#HL`gie,, XɜX@(EbXXEvyo7eYdV-YPKԲR| ^] SUB3= J/`bkEnG$NNO8Ao1 6p~zbxHA}`..p >#uuUj4f[?As0 j7/7EB*FNX)ڠ:$TNޠ#p$ZYPK7-ǐbiIENDB`cecilia5-5.4.1/images/path-hover-trans.png000066400000000000000000000005771372272363700204010ustar00rootroot00000000000000PNG  IHDR(sBIT|d pHYs B4!IDATX햡N1=ı'uD_*Q  H,l w fv٢k̠kydA-YPbJqQyUY(1c}( jE>$8(_NO8A!\"pyOm`@%vZ%p l >gXׁ>iT{?As0 j7o|Tp&4qk$ L;yijw+z,08m%hIENDB`cecilia5-5.4.1/images/path-normal-trans.png000066400000000000000000000003561372272363700205410ustar00rootroot00000000000000PNG  IHDR(sBIT|d pHYs B4IDATX hޚ=)ڡih {+|"y)&7-j3#pKJ)ܿCN/Ǎ uCZW%j36j QgH_Lf@m/xIENDB`cecilia5-5.4.1/images/pencil-click-trans.png000066400000000000000000000007171372272363700206550ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4qIDAT8ԽkTAg?R,FFS?4BN ]1M 4b66ZڤuXXkw^"=g`<IQڿ }Kl0x+cm C<Fm5ZO}/rs{& T˛&wp5TKK<MZpÖ8V|`+trXN,u`Ztqa{z;!:IENDB`cecilia5-5.4.1/images/pencil-hover-trans.png000066400000000000000000000007161372272363700207120ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4pIDAT8=kUA?l$ł6j4kH5`g b(6*&m)D,kqnO#8ZIa?XBIo [4Km͒ELW펃]Rbs}렗b*dc_\oFI\onc1+8ٮ\R>fFW'a/:求W\}mJ _p/z9OvKIENDB`cecilia5-5.4.1/images/pencil-normal-trans.png000066400000000000000000000005451372272363700210570ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4IDAT8-KDAb06,h- fd0(b?| &E63U.̜g3gO $I$EDg*UVl)m= nu-Fq Wo' !SDC0Xzب N p?yuM\⨢mޒ,%HRg%i%6)UU8 Չ\QK~'`0}`?J"$!IENDB`cecilia5-5.4.1/images/play-hover-trans.png000066400000000000000000000004621372272363700204030ustar00rootroot00000000000000PNG  IHDRmJsBIT|d pHYs B4IDAT8K 0;s ¡݈d)vM8Lb纅AG^NQU+Bƅ"{"ƅ1VK)gE!Boߗyu>G&2.]ׄZ晔y&XPc]i"Ik*m Ky.Eb#ARuZ-Ϡ<ƅP>$"rFU}*׬??_wsIENDB`cecilia5-5.4.1/images/play-normal-trans.png000066400000000000000000000004071372272363700205470ustar00rootroot00000000000000PNG  IHDRmJsBIT|d pHYs B4IDAT8Q 0Dgb,ͺ)I,X %}L'M) {(Bk @OiAXi=HRqI%=;X0jCf7I1g=h](Cq_|3owIENDB`cecilia5-5.4.1/images/pointer-click-trans.png000066400000000000000000000006631372272363700210630ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4UIDAT81kU1_ť:9Jup#t0 nNR,NũK.N@Phpr9r>Sodn<N̔nb3r0u!0q#TM uu{Oss:cwrip5u^T!b%ղUVLw?}xrxN\1㵦7sK-iU'C3O3;o@IENDB`cecilia5-5.4.1/images/pointer-hover-trans.png000066400000000000000000000007021372272363700211130ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4dIDAT8Aϕ(PJݸean"eac,PsWNMdNJ͐`H"[+[8jw;37RfJ r݊3xJ1 pv\0r=7 pn#T ^k%LP[qƕu8D]l%}{ﭤ/"ד}K(tp/#׽uC8 JHhg;Cxn}i1\'\:JW#ׅ^V/Gi%= lŃu乿^Z\7Y[؆Vu9\i\cj`}{x物4-Me !LK@GÌj'Re0 ( ix 1` @O#%v'j[N9H]V\-N軭ިSQ *UަpA*0)# Ih@!JSJ6.N=T5UCԸz ZSwaJ ;18I[;ojV"\ &g`fԂh]! <ݰ~uIENDB`cecilia5-5.4.1/images/previous_24.png000066400000000000000000000031531372272363700173510ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B4 IDATHۏW/.30@r6iI)j/DS66&ԋ& SBlR6С8-G)0tfOZˋS&i ~|B<8d{wl61Fىn߸و>BHql޽N'ZihL4Nk(ܞd^ß.Pnt1(h|; `ڛ;8wEH%Vec`s2Q5(F fĢ8mX?srM_,JJ zA7MDTZbl LЬ+?ˇ0gJvcpky0(PFԋ5?$mDlPFKCTdCgۀa-]=Q`P(yaˏo졥XFrNE[)( ]C巎܏fIWn۔ݼ>WAB3ީsy 5n2EnRef<@7wEɞ\3s滶7{j??}rU-m,c!cXm=Lfh%Qnc5MӢvʮٺ Cc0łF iIM +!LIs;t{RgOlc[6ƀ/#Rr ]X-(`fC߷AL,C2H $$0ɳkaY| |CVAhRT-c\_F[fyH$ƴ]"8vmK")ܩDvdU;ra }JVcۊ?=ʯ8Z6ԭl ˲hD-Rag94e)l~Cc}v}xx~OxiD:NvTg>V:Q'(C -TPVu#lZ˒^ =khT#.V.Й Lj{ ֊7G`nc>G ܿwH@b%=/bYR$zKH!0AG,A*]VhTP%_}\j0U%m!- i-F⩐smdұ ?F" =ld~ ӡHyI?$Nd_yP@U bjEŊfb[vpx1bq?$p\|#\zHeo⫗w^4WSŅ_[|uIQRZ8Ƒqlv)Y/yW.v} ȭGŮ!cY(¶% cI:)Zg|7G2>ֽ!7'3; xʤcdVrĨ>Ge6+,bДQL_SfIENDB`cecilia5-5.4.1/images/process-hover-trans.png000066400000000000000000000012251372272363700211120ustar00rootroot00000000000000PNG  IHDRsBIT|d pHYs B47IDATHKMQ~107#L&RqJ@I) # DʖJ&$=ٗ:sCVgu{Z?lDz5* YtxҊC|7[./x9"ԛ%49+VqZ!_ J cC<ƣ0A.ž|4 \qܺx@Tz~"j wBH뽂8^C<ݘ';6!MWwW<*4%|Qsƹ]s[^xSJua:Vɫ'{=caj=Ƌ&8~Zr.Ő&~ 7[ :{4q(I?P KgxE+p|G8ifNe ;yAnR4Y8m!MGvd,2J~}!M.4[sB\+eUyؕnC4+$kH*xZ8Fʲl/vOG27RkIENDB`cecilia5-5.4.1/images/process-normal-trans.png000066400000000000000000000015731372272363700212650ustar00rootroot00000000000000PNG  IHDRsBIT|d pHYs B4IDATHU_HSq6rZ-} 0"0#̔?XAEEt+D^f/=A ZiDDʩ:knzzȻt B{~?~9.GCDЩ?4Mҋ!#+3sve'&7F(jt:@QZHH Vu۱,w(/;*֖UTNqMM"b(p8N{^Mxb=[QXǟē\s6vۄ^P /:6qτ=1x!_!7o+w|[T/$ݹI<ϩu_e@cy#J.bX P+v!J>\*4(gxD30 `2] ZЁ˲"4O,ю&LbdI(6BGᶧ<_AdXdTdP|CX cBF J 8x,pԀP^` Su=c" z(쵁'\_rZv4&828dς:pzuuIRԻNr^TOiip~DbIENDB`cecilia5-5.4.1/images/reset-hover-trans.png000066400000000000000000000006141372272363700205570ustar00rootroot00000000000000PNG  IHDRVΎWsBIT|d pHYs B4.IDAT8+KQBA 4]A .TG_A1M1"Eab,FYpugw ts/evDG[(umQ29a p4_(QG ,qx[Mqz.PN=Co8ʇյP%-P >Auҕd8-&D1V8,zYc'OpTPcx DI7>dKL"8kY< U?Z|aIENDB`cecilia5-5.4.1/images/reset-normal-trans.png000066400000000000000000000005561372272363700207310ustar00rootroot00000000000000PNG  IHDRVΎWsBIT|d pHYs B4IDAT8͔=/DA]H4@Fz~h"X^/l$*{QlѐlIf\ܹnb#Ng>9TP  <`Z-ҬzvmuRSV)49H3ez]UcUm1P_}U+&,i*zI0%4K%=`X>ZKj `8Ⱦ{8I+PWӔd'CiC}PA&Uo4 IENDB`cecilia5-5.4.1/images/save-hover-trans.png000066400000000000000000000005001372272363700203650ustar00rootroot00000000000000PNG  IHDRVΎWsBIT|d pHYs B4IDAT8͓K 0XYUأ6A(:.Ld] _P'5¤"KJb2_,&Uy ^ndmMoR~m-tj:Pי"KffqmTN@Yd@RS ǀwiM+rQ tȝC#M߀ 0ٿpQ=7Etoyk1?QIENDB`cecilia5-5.4.1/images/save-normal-trans.png000066400000000000000000000004361372272363700205420ustar00rootroot00000000000000PNG  IHDRVΎWsBIT|d pHYs B4IDAT8Փ @ Eψ("'>3s/C ]腐Ln3!HuB P"Q9D W}݉ffV.98^4P8ʦ*omA#wlG`4C @ YU=ʘ(:ԥm u^(sz-hSS'`(~h6fNIENDB`cecilia5-5.4.1/images/show-hover-trans.png000066400000000000000000000005201372272363700204110ustar00rootroot00000000000000PNG  IHDRVΎWsBIT|d pHYs B4IDAT8ӱNAėx%5[XyH`+xXSf80N3g9N.݋P7;(-:mzC8n^H1aQe5 ô4Ű V)Y֬gMyߢ5}SrEY=`S 㢬be-[A 1![7Xüw~] \IENDB`cecilia5-5.4.1/images/show-normal-trans.png000066400000000000000000000004531372272363700205630ustar00rootroot00000000000000PNG  IHDRVΎWsBIT|d pHYs B4IDAT8ݓ @ E_Jv sdZH6LET)DqaɊ;&tFӁ\+$1)΁<Ik潴Uf)}o0Suw 202ũr3ZFX,Q+7f[k)|m`́WbG$mg~F}GC -FkY:vIENDB`cecilia5-5.4.1/images/time-hover-trans.png000066400000000000000000000006531372272363700203760ustar00rootroot00000000000000PNG  IHDRmJsBIT|d pHYs B4MIDAT8ԱnA#dQ2E\tP[!J鰄,Q -W iBADalqiVڙ|3Z,6a[l`3IY6Ŷzy#t0G\_ds5gIZt,Gǘb%c"  rE&M0X)Њ@1O1~YU#E q=G*m{{_[D.|@0[qX<^b5W1-$Opm" W"9C\b_=ZP Swә5]Եc KIENDB`cecilia5-5.4.1/images/time-normal-trans.png000066400000000000000000000005421372272363700205400ustar00rootroot00000000000000PNG  IHDRmJsBIT|d pHYs B4IDAT8ݔJPFOM#lg#l_c7 6A;\Q8;+(, 7L2g>f&Pنl 9ojR}V\Kf]9]zGn1{uQ>ƷY2AP&j"ݤ+ўGE{;ުhrߦLV@ٱ%E ` 7=jԓ4<L}P`O:ة:NTrU!-@^_$Ifo;bo`烲IENDB`cecilia5-5.4.1/images/up_24.png000066400000000000000000000021001372272363700161100ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B4IDATHˋ\EsN=1H1f!f"Ap^ɘ Ѝ KD(F3BIL?*$=E*;^vos3޻Տ?o`2ڱ?}vc/-&wmx]wrvx|W)I ;B ~" OI' @hΜ{Cx(ܥWkLJ''V)GzxH^X C6תQ:/`,-mUv,GgTj+ K! |Z5NZ' k~M|=<)^}zÕF7 0jsO\҉iHjҢkL;iLLq(p`|?o\ܫW>9:qܖ0׿u봒6p戜#2dA{btyFO_iܫpӥ_>&> *v.=Ĺgn?¾G[w?p!x,t1) o/ӼE)/S03L9eomqJc7t"hWpb*Ng94F[M!u=r,%.Ȕ"'k@" E9Q S *̎4(p2N6AE]8ӼnJَ{JDJ,ynLp\0ijz)˻3#`큕Hܙ;> *A~x)D18f$.PbQbH D8VV;BKH0:,o:Unn'3=Tn60G俰bR1~gMj#IENDB`cecilia5-5.4.1/images/vu-metre-dark2.png000066400000000000000000000005251372272363700177350ustar00rootroot00000000000000PNG  IHDRevsBITO pHYs B4IDATHc``F /r3?ĕO^kÆUӻOn2.O)b}oX>QBKDe^;KFO #XʞЁNl'/iH`꿾&vO|OKۿ89e뿤3+`+3@9;@^[ '6ٜ`@hr&Q0_&IENDB`cecilia5-5.4.1/images/vu-metre2.png000066400000000000000000000005121372272363700170120ustar00rootroot00000000000000PNG  IHDRevsBITOIDATHc``FmX|s8jg}wbV]zפ=O_z]Uц_}WO*{s N1NyW JHc=SqZ9ZYPW쉜cEJXe]`͂UvN.s֗Бy7Y k\سn9'[Ao=  wBj% \OGXgUJ u!&yQ򡟧A)0 ) [Ucvu9na/[N\Vb,o\_[KW$6_M/}cws4MCIENDB`cecilia5-5.4.1/images/xfade-power.png000066400000000000000000000006501372272363700174100ustar00rootroot00000000000000PNG  IHDRH-sBIT|d pHYs B4JIDAT(mJAϬxňpQ  *> T!7Rѫib&tlv/?4g̙ #EJ$VJIOx$ lz2ВP̀Ix&,"ms| J)"⏤'IIU%<~pX}~Թ-% p q݌_nN/$`3˟xt 'QQ dW/mB Jwz}-k۶KJ񹺚_g kOD|yRIENDB`cecilia5-5.4.1/images/xfade-sigmoid.png000066400000000000000000000006251372272363700177110ustar00rootroot00000000000000PNG  IHDRH-sBIT|d pHYs B47IDAT(u=KaihQpi,D:(7DCMQGKKSj`)hh9W6Ώ<.z 8UuZ ,G U}HlSuQ'K`$z!Nv.[sg7lWU?4KM^K=z#˭uFdXUu<&OGUUꗿ>ȟvdIENDB`cecilia5-5.4.1/images/zoom-click-trans.png000066400000000000000000000010161372272363700203600ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B4IDAT8ϋMq 2dbvdvVVjV6!l4ˆ( =J.q-Η=ŞNO}sV۵ZR Y pӇLQσޒC,.bSs+.^$-`"=7;Y>֘aQ6/և[l,8)B*scjߏ 睚ޱMrUs|fP68Am1 ^|scIENDB`cecilia5-5.4.1/images/zoom-normal-trans.png000066400000000000000000000006211372272363700205640ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs B43IDAT8͔JCAFϕ`!HX FRka'Hcc% | JA BE,4E,D˱ěͅDp`3 3FJ `!*P=fu852@jz lG[TsW=Wڲz/`E}Pg{]PWf/)Pn#:b-QGu_ @`h6 0-SEa쑺fh3sR_&z·^/ZWQalkEy,j+tz ɪ:=Qn6ybx8DIENDB`cecilia5-5.4.1/release_notes/000077500000000000000000000000001372272363700160435ustar00rootroot00000000000000cecilia5-5.4.1/release_notes/release_notes_5.4.0.txt000066400000000000000000000063231372272363700221640ustar00rootroot00000000000000Cecilia, 25è anniversaire! La première version du logiciel Cecilia a été développée en 1995, il y a déjà 25 ans, par Jean Piché et Alexandre Burton à la faculté de Musique de l'Université de Montréal. Un grand merci à vous deux pour ce bijou de logiciel sonore, un incontournable dans le monde du traitement sonore depuis plus de deux décénies! Cela fait maintenant une douzaine d'années que j'ai repris le projet sous mon aile et en assure la pérennité. Bien que lui ayant fait subir quelques transformations assez radicales, notament le passage de Csound à pyo comme moteur de traitement du son et la réécriture complète de l'interface graphique avec Python/WxPython, le rôle principal du logiciel reste inchangé: c'est une boîte à outils exceptionnelle pour l'exploration du traitement de signal audio. Dans l'idée de souligner l'immense contribution de Jean au secteur de composition de la faculté de Musique et à la communauté électro-acoustique en général, je suis très heureux d'annoncer la sortie de Cecilia 5.4.0, disponible en téléchargement sur AjaxSoundStudio: http://ajaxsoundstudio.com/software/cecilia/ Quantité de bogues et de plantages ont été corrigés, ce qui en fait une des versions la plus stable de l'histoire de Cecilia (même sous Windows!). Quelques nouveaux modules ont été créés pour l'occasion: AutoModFilter, Binaural, StateVar2, MatrixReverb, WaveScanSynth et Stutterer. En vous souhaitant bien du plaisir à tripatouiller le son, Olivier Documentation en ligne: http://ajaxsoundstudio.com/cecilia5doc/ Sources du logiciels: https://github.com/belangeo/cecilia5 Gestionnaire des bogues: https://github.com/belangeo/cecilia5/issues =========== Cecilia, 25th birthday! Hi, The first version of the Cecilia software was developed in 1995, 25 years ago, by Jean Piché and Alexandre Burton at the Faculty of Music of the Université de Montréal. A big thank you to both of you for this jewel of sound software, a must in the world of sound processing for more than two decades! It's been a dozen years now that I've taken the project under my wing and I've ensured its continuity. Although it has undergone some rather radical transformations, notably the passage from Csound to pyo as a sound processing engine and the complete rewriting of the graphical interface with Python/WxPython, the main role of the software remains unchanged: it is an exceptional toolbox for exploring audio signal processing. In order to highlight Jean's immense contribution to the composition sector of the Faculty of Music and to the electro-acoustic community in general, I am very pleased to announce the release of Cecilia 5.4.0, available for download on AjaxSoundStudio: http://ajaxsoundstudio.com/software/cecilia/ A lot of bugs and crashes have been fixed, making it one of the most stable versions in Cecilia's history (even under Windows!). Some new modules have been created for the occasion: AutoModFilter, Binaural, StateVar2, MatrixReverb, WaveScanSynth and Stutterer. Wishing you a lot of fun tweaking the sound, Olivier Online documentation: http://ajaxsoundstudio.com/cecilia5doc/ Software sources: https://github.com/belangeo/cecilia5 Bug tracker: https://github.com/belangeo/cecilia5/issues cecilia5-5.4.1/scripts/000077500000000000000000000000001372272363700147025ustar00rootroot00000000000000cecilia5-5.4.1/scripts/Build_routine_Win32.py000066400000000000000000000016641372272363700210510ustar00rootroot00000000000000import os flags = "--clean -F -c" hidden = "--hidden-import wx.adv --hidden-import wx.html --hidden-import wx.xml" icon = "--icon=Resources\Cecilia5.ico" os.system('pyinstaller %s %s %s "Cecilia5.py"' % (flags, hidden, icon)) os.system("git checkout-index -a -f --prefix=Cecilia5_Win/") os.system("copy dist\Cecilia5.exe Cecilia5_Win /Y") os.system("rmdir /Q /S Cecilia5_Win\scripts") os.system("rmdir /Q /S Cecilia5_Win\doc-en") os.system("rmdir /Q /S Cecilia5_Win\images") os.system("rmdir /Q /S Cecilia5_Win\\release_notes") os.remove("Cecilia5_Win/Cecilia5.py") os.remove("Cecilia5_Win/.gitignore") os.remove("Cecilia5_Win/setup.py") os.remove("Cecilia5_Win/Resources/Cecilia5.icns") os.remove("Cecilia5_Win/Resources/CeciliaFileIcon5.icns") os.remove("Cecilia5.spec") os.system("rmdir /Q /S build") os.system("rmdir /Q /S dist") for f in os.listdir(os.getcwd()): if f.startswith("warn") or f.startswith("logdict"): os.remove(f) cecilia5-5.4.1/scripts/Cecilia5_512.png000066400000000000000000005533411372272363700174300ustar00rootroot00000000000000PNG  IHDRxsBIT|d pHYs  ~tEXtCreation Time11/27/11 DtEXtSoftwareAdobe Fireworks CS5q6 prVWx[r#62 Pfd X>NM_9sIJ]^ t?W"5" ·C3NtNj#0ሞpz0*g/dx#`:Ri$9G9|C2(rʨř<ۮ# ^@VzlD6" igBbd5q+wv bˌ+]l)e~7z%!~f:.؆lQiYy$*d D ߢE{f;Vq'Y ؂oy^ˀ3l#k `-!gTcm"J̥G4,>v]O# A2YɡFȩ;ܰ. 8Vyn(~(٭8JcùЁ趬Z{̓ߌo9Gck]wcs=߬Z;贪o݆Ҧ8Ƒ!"<1}/N|dps<@wQe. ؀>S0(\EzH?Xt:Cp0 *d9·ިśV!՚A|8/D=>@gY*jC;VtM a P]Šz,T?0+]aki *P81*c.}*u:H ŌU|O~1W?-H90OMDB51RaD)AiR)p]0S>[xJ=/əu} 4;d\[|LzXFD.MUr܋yek:g7t>׍,X݂Qˌw̨DfUǾZ, F,gNvUD\hIzan*wEܪr6y4q/pPLJ J}{U͐AUH(7EߋXuɫ"]D%%$O8~Qn!듶2xnmLiq׳_\ "(;!~gw  OVQ_-wAET+;~0OSZm_?I%/(M9 O)WD W?qҏȦgxtC;wM[|Tby3)8{y. l[ys*ǵTu!0g߈L言 1zJ4.7OڵAU<ř/oC$ ]nz餡ךz pBlOŜ*.} fHþ!P$JpWg(Rc`uw7ċ:H(i q> /\ {m72*o,~P-ZO_g T|/nևCT5*| Uj}R}*H)0QX|nV~fQJĬfuzwQ'cԁN#? aoa56qƴ(^w11 []meIW& igR]ЯܞWrU[V蕢iFUV%GȫH*,`?V/|V]˶]~tym pUJٯT?'F fU7-|0_8f}]AڱIlou̽W"8S*BWe=R]Y` Vs.Vǂ"OQfh-l2F/=8GXll=b_PY ^xaq wnqC0a}:*8 :ԉC&yCҧ372 z* ^ kŰLݟ/Wa0\Rz.Ek[^8 8EKnx/Ћ}8A_gz4nхD9^~\3.uj; @=^]xEޫXlq:%%pQi-[jĶ|Ȩ?% +x7^D0@x/MT??[쨥b9A/x>%_ A dW^Br{ot8 9c bm *n={S*`~ACwۘk;!miUkV\#ۨzOq% ai8'~P_RtFL,֊wGm[R ջ]/J%|;ew;Yㄞ5k}!>Wp22EOp<VRY`SbvV?OuŰ[s{u1Q]>k e>鬙ռ,7Փv_|2s{n}EίՊ??|3wg]o~`~?.'^_ |ܛ}1>oowQu}m~~<|A tQ/pD*4HmkBF)3Z$mkTSx}w7'$ĉ=svS5ޏ(J!)GK6OvBFM6@Ilw4cohByՋ^jx;bNoWKz:4L[ Ea{~1qp8?z{UinhCso=:p?;wWqcmoE<+o kW/;^/z1rsށ"tjbsH&&#eW)$>Ϧ}`ҁ>$H,e45v{8~txWČ]3v[=7.l-6fɄ`f l xF'to/}vOh';JwoA Z t:1GYiH:4 IlD~%EC1~#=e`M+{d^[ 5ȵ׮BD̡>k7Z6%mNs@.:lN$%ɹf2cG6b /Pz Qj6h5dI!4xO@ h4(6FݙH F:+%L;n.aosX$X_I6/O z%@if|CNhzMP  Bf%+pC7@ 6[di\$$xlB>o فD )9P&Se:PnH{6@Wv=% avi PcrMBa RI4lp  F:66S'먼СBM݂-CwMW(ovQlYwv%F?X1XG >%(QN*iqkF',T(ґ,c杲SIݑz6@rZ s50p{\I &y9|}ep&#+ 6snjJߙx`dof3sX&rRęM|>Ǒ/af֪1<^'f5CaJA/C+hXte|8enT$@1(]P(DF>'qI*AdI)x8\f>3qNOb+;37p>#{VҐ D-I 9cuGDVh%-q uF7j==T_|ssw3v \,Ņq#/Ke%R_6VxtA?mK Wb`qQ,P[e7Ӓ>v$zFf%3#\&R/7ײU+P l S'pXJ4=a1]8Tw6@Ȱq$reơ 86χ^5,zUSkɆFVc#Ypcb.e=[=<iG0CʉH)/ߓ<@3 4i\o!ފN>NN|C)L7( ] DN"e@(Wr%y`dΰцfxP6X򈘅vdktDk (;BrQY&_K DQIz< n٘-vyfNS8,x-j4t6 g-whO`mDVcb7c|tC"{][rOӜS;OK)84]oPJ(-r5(CW'q=iNmjز+8[ x5A7%LL_{(b(`(b +q0 +).olE᪣Ý#Z}.sD7 ;[yE5鈺wD :{(^oĵd}9Q $S F+g+lV$_ DO<|tvk)-Ƥ Fj dWd~|b%.W!]?a#t td⊤ %ד .K~e Dum|Od Ts)/F|7U=glְ#Ѱa7h؇yg-6yYú3ECGRf{,en Z0FX⵴ZIF8=8i[@XyDROK=^9jq,_GĸD:j oa>߱<wЃ-n? SS 2C28yaOg ^HO]R80f]`O-z7EA^ypѦSg67dU:lmˆ?\w?Ѷkq9 Ň or49Ԟ}D=]Wmn\57y R8"3\G EMƟ\;jwKk7^{KDZ[EU] 3&iD3{+x.< Vk@KyՇs'c]㗕o(bkWFp+UT^B(kVɝhwc{e )f<%.4|Rrh?tskf0s/+dWLeOj%7ۓ4:,۬DQ7/{ZjQޯO:MyJ5|?ikH`+̐Gq x +6d-K8k3D͑oygEE:y~s`ήumlٵήu~ uvP}еm е_/xYkZ;ߺvj\kZ;;hFso];εv:vC55to]C5t46gXkZCߺ6umk 9kI#X#jB:ȻZg]G:6:󋼫5}u]k m?ȻZ;\Gڹ.󈼫}u]kn|>w5t]4yWkȻZCm˿֞NBCܵ]m=5BB~hRlȈ.xH{%4t~*KdYG@SȊ)vM*&9IKq6"$ ͥЕՌJʚ\#ˤc(߱n40mSiꊍ$"o2"d(bD)xʥ.C+zcnHP5.yg- &׵dx1JX"~AFK>k*J@S (/ﰥ.YC?xeg^IO-SoZ-:W̟Y~?_W)گ~ӪگY{=ZoWeC}zW ɲ=)Aߔ"aGB9"byLu 7=$H`B;?#7L[,Ы\kZKZOK?m|67eMs/˿w^Mim"ky[Hk7% 9)g΃Et>`oFfNK Nm=w.|:?_dQH?Kyqo4GJKhw%cJ{&^.qݩKkzX\OQȄNpd\s]5"8_9Z{X9]6%7%3#h|tR++7o U{=7u/oZ,HCC{]3 v1,jյFO\nX72IDl1N#s.f~$/79Taγ(>,E-'c-L] =5sTcGN2zyKVp__[j -ﰜʧ,ѱOk2Zx$i?6T>[?h?ز?Q Q0F|Pfp1ڒ/O 6E3+3d࿭|vuW$<x\q{{<.FoPߑvXi޵Buܿ4e_uAhy_XK(ОaD,ܴi qkq-J3p7!K/#Cc׭ܰ<󬻤MP~kW+,{ɝJY׻k<*GUNz'+Wz; 2|Hzϊ+a ";c.7H*83/VXUkꗌ=.;m}铿\_/b! W{`Y~ iJ/=SΐOT3> 3n^i(AꕆjԐ]+vʵ6g8?=M5Z׺+)m.;)P食ۄ}#/͉ɴwy+iMokzP[xQR`5kc`_ն|t}{>2'TێВMwy2rݗvoWxvWpQ/hY?ݝ|YGɢ[΁ %Ep.;_\Y{G8_0]ڧ+o9b/Yh -Z ~od9&'2xS%<'݋,^-<;%"?^*o>eW)$>Ϧ}`ҁ>$H,:f_ŎR1_bV^xuD;^X^^oĵd18o}/FaAx.ܓz?A~8Z W|Qv睛hwp9Ʋx=£ /z/A&݁<>m=IWzggvژ!L?x";ɰϏxt0@:F%̻b⼃E'z(_GG 3F;Brqtp]̦}{Ļr9?wߵX87-K,d]x[^7?$D4MIb/[uv=xlSL̴soǒz,w:Yvp{`*2T?mtSR&q~N~|,>騥Q?|H`Qdv?ϭ= sBClX㯈& !A\ 0%@-Zےn<-l57z[n\ÚO5UJ߾gnOGc'! 8, Ft18=P3IYp8_0 =8xtpk C=yqx<8puy[ |%cqc)G>?DI 4NH{X G3Hbχg| yΎ`9ՄW!}N Y<#p.B'v-v;A^'M/2N"w8>'yQ'G{m^֣6CoDrгZ"Pv@|Ppx?&O;MHp,9 x,P ( ($P0z*o))HpB+ ~3IHNt ooƱ:fofofofo6MS͚͚͚۬۬MwrY6YŁ/X4=?Rzӗ& K~wqGȥ$ԦWPAʘAiM/Jf%M|/>v÷3ܹR*gp'yΧpy^aoIGǐ͘eYgٛ>otn|hCw-P ;?2g5b~XJAAR;dx<̒yGhVA^4 ?UhgT>I{\i'?>ȝ/N ɯr_]Dž .OP|h>[Ç$D}A! B4]pÏ؊!B;stSeDY͈fD1ίcDvVX͈2|n׌h͈n-fD?qZ?vǬ^2?{?$~3΂W1[&}*yRʹ ׿oKk_|nxGϪn]ި<==x۾h>ChIϡ~KOf<2fiȋ}?&$A\|(/ ?lb=xAT.^3N/ sI\秱CCUfl2/ |U'\@$H| kP"Kw@VQ3f, ,4ֳa^3#%5,˚aE\Ue3!0TG'hB?J||$$|Ҥ$aUg3#ـD, UqR YEI>Iӏ8Œ1('^@g ,𪻺O"OX}OLC?QڄH>1OUoF :tf) 2EyE X3-*+L}Ϣ^3e C4O5#H||8Mju< c>~/A+!ڇ0V2Ҫ|^ u<6 !' T;,䙨(iZGqẏ0JUHӈOTBO *4Ҝ&@Z((P6Q4nHeե I@v( 6=6n?e5S~H W Q<C Ue6DM3 W Q; U!=!|ROދUQ C+|r-3J2jZoHbe>)*| 'tШ̇=(5Xe>)F!T SC:WCfU QOɭ!5*!G('VYCjVCf%]?>&}e6T>ĐPS4CTfCa U Q>7ԝ\&4ϴ2y4OxHlW20UDDhŐPOO]̆:Z:uV{%o |ǂ0S^2&a&8 C06J %4)Q|);gaN:u&dYT/ٔ %,iIė ;W98R=J #0ܔ?cRr(d0؀JWN?Lɡ(SÍ{Nz7X,s& (;Z #sD`%̋w*?ؤS|2W*7߁h"'~zQqs݁3bcjĞ$QEG2t ))Oy4S=5۳( c(Js+ƥL(>]>k^RE!tgA=M0A)Þx\Q}0y&Ear.˧&([!s%.C<{7/ Y)$J)X0rm[1 )x|5Q @5|d%34 `ͭ])R^ϙXOR(ɋ$At s/zJF3qbQzw2p3ІD>QsD%AA)ȃ. |8^"b+X(3ʠ{*P>2IFDSJHr㓖 %*!7NiI vȍVZf?F,_cܨ%Si$d*G&r mDn9zDdgSF1FqL搯qEn$ihddE[F3FLEnDijd9cF4FqMQFn\idhi6N9MF|Xk#dxq8ԃF9rNqGAh+D"ؕvVBG4'Zёy2GM'$ݑ{2(Y9rOGi7i.ĢӍ{ZsF= ژ=YmscImucY! pci*.qcy3Rrc̉y2c4čy2u4ƍy2Uz^YԐPfdDéaƒ`F$mf/BF, F4͌С!MF*bF& tP̈¤ќښOFpa{3]3c}0Kߌp`!Xf';!7#`,2e̐rӑ2Of1K'3̓ ܧ3%XzZZ&aQii݆F's!H'seK'scM'c參O'c!@'c]B'c9Cy'c՘ [D4ͅ"XSQX@QΉ.8t )dt`@)dtdc*u Wu H'~ցE7z^Zbik띭g= vLaV(0\;:a*0Vff2V;L n3Ƃ8T 0j,?^sz85T9FS]"8DPj>KƆ*F@>rJV3(Q26| }e6HmwUM5`W/^>[ݮ \@7w><vϵKx~iw*=dmk{c_鮖urqEi0b\O-~q( vDkG6@(<>^ tWn0T~3߂rs*;!(d?+d 1~f ./vWtßVB?ıL' oosK&=yz0Bw4L19Pu&H$p )dH@ttu׺:Tx[e򎺩)\ެ7Go]>wя0/E'1D̰:ֱ1Q $jkI?z?ڐ]7F[ox1RϒH$f4;7-ŜJMdW|&z6@rZ s50p{\I &y9|}ep&#+ 6snjJߙx`dof3sX&rRęM|>Ǒ/af֪1<X'f5~*tJ, 5ZBѕ;pZ/E1(fE44 GQWI*AdI)x8\f>3qNOb+;37p>#{VVsƨbАK"[j]@dڍnP}I[R,n8|ZFXBoɮLJ*WR.ط [ap KNK856tpJ-h^VC9/p0LLqc9&w*Dp _/e>lQQgu9|)aPIʲѓC7ppl1}k"Y~㪩dH#1FZJ811ò#e!?MOIFsU4wf.7oE'pB'Rj'^{Z.z"C'RNR2Gm~+ C]x2QgKhC G3JC_(^qDhr,yDg;5:5ch9Ԩ?p,%?O2_6"Ifc/9Mh沌㵨ٜw+uԏީ=Z?cܜq u}rl2Sb=Ms^Nw?-tA)U{^Hˍ֠ H =^\}9aW|b/&(E18rĸC CAm/S{c}M[ ZaXW#[ѦB'(mq *<qQ!,:1?Jvb) iKR T2Wt8jDPza ʄј/F=4\p!|Ko}:=UݺDmaa;܅*Ct[R)D~<"e{)1 0V|&ِLUdq{m[Q+78V!|<5N DpdX?YJr9rPq|nhi 2R]IK;^ȯXr55~c|MoQcUNuiz]?-q/U*# F+Uv ;Vy'ʔUqC'gh;8DvK}ai0IR2<4P?TJWQ"`#U4^c+mU>r})H$JmVs+ct{9l#BaҔrd)Fm9eD:LʕCa.>.X@iB̫@*DBc7">ўZ y8' ΪUbx7>+ M䳂WxS”^(ja"J*( B FZ[t::9GNB[a:GNpÀ_W$Q{GԠ^O\;9OݗK9`IrfEE K37Jg罶»") >jLiOvE{`-VW ^o'p%pÿL_WтAQƠ'6hx·ڝ?ҚW޿VQU̟I +nx !(e?}^a7ܻ<w{er(abUڟ#ʯ=m<!SEٓZ %r6,}?%ƍ+z| (--i{?}+SNSyRb'Oڼ@; 3Q.ek;^͹~|= ?ks[izyQQ;癩Cj寸-Xk}:ۯuvkmHx5to[Cǵ5t-44^ڹηZ;ڹڹQ,k\k[Φskf k ]k[Ak ]kh зM][kZCD?;V{ȻZG::Cgבwήu"j }4tyWkZCh+A;בwvv<"j|syWkZ;O]#j ]kh D>h:е.4tgS7<~&wmW[@e_z#z282 {v ݠ߻J55Y֑ mE<=#*{]g'k1Ozs:FXڞHiF ajնykkKfskKӶD.iH:?9ѵu:}ѫuBߐ6|_{M8>^ӟh%]uyp5C9CbOQZg#U:;9)ܿUuK?-xK tK\1oKr12_T}՘Zzy_{R U׶;} .AJ1J4,)s@gnlGhCMVUzXl"INwjFR-Bs)te5f52i8w3 {!u.sTb2`@&~} x8rЊiz(T{Ks ȿu>1ryGj'Yy^6B>GyђϚJi5U,ʢOw.nb$g|)wP]{*/uWyDwP&Zޫ⭴2#1g<#7my{~{i3pMҺFXu+7,$<.iZՊ0^mrfczZ#- dU3I յs <+RƳvxz;FFgHhNp=<> =% oV՚%#gxN[}_25l{]|Pw~nv>]fZ9OO)txygZ׺{ulcݝ|YGɢ͵nt%;u \+v gtkEkAҿ f>c($pzh.^`:“ :xMHzxDw/x@>苗Dr3'NxE]\[/}qx*.~sȓh8o!G]DnxQ[`ʜ 訏G$wL1zq"c=yErs4'+*^`2 g$v >Nȫd4td77k=-mQQg0bp?zLTX#ċ(I\tO. ,8<@$jTcp/<;8͚ ⤪V/G;CЋkz%Ep!=B\D\w>n~b҉W2K3WJb/[uv=8;|aRA3`śjE_+;Q^;?`G3n|05׫[m7ϴ]?ϤNL},N緝ߟ.yW4;CCljXflwk+c-7(u %yi^b Jի-.fQsמ趭5F?xd}P]XSt9f~_ꝀͲ`MsN5%i  `qCG-?ċ8ܓqǃWǞ5X8w4$|xƇ˝w ]\MqBzD`i1 'h"tbKb3(95~,T)xLNh9MY.Nx{1iyre=jcQ`=[H$/=|%(e ;·~NMvp#nGsyY ,P4,PJI`2TޜSR'Vf ~3b7-ހߌc8u~ӫ͚͚͚͚߬߬߬߬m&Ts5Ys5Ys5is:6g$ j?m _p?$hz~/M~ӗ&?<& 'K%IM41 ^/(J^r}F;oqg ץT4b.OO^ޒ02!K1˲,Z87}F8^χZ v(~dk/wEWx.%(RFѬ Sh:$Β}>g]8lc"8)p&?T~u .t&P'7Ű˃.EA%040Xya 8'6 nƉ൭v⫟*>^/=Ҽƻ|.U+򲥚io3ޖ׌|=U}Q [yGi{v{ nI 5I3H fFX⤬ ,|q*%Q3bѫQ=O'@YUwuqCE* $~) K-:|b 0^ߌAu>Di3Re>!|zf ZU>WEy B <ջX7Y'A8D #?|f@ -hkFP8 q'yO)|^|WCae>!UA@rym ,CO6 R!wY3Q?Q҄/T?1ajA̅hZTh"9cMl,3)i܄8˪Kݓ@QlVzlTTQ=  j++!x2x 0l)fH2v A)%Cz.C*ԕ4 i''AޫW3ZfHPe>)ސ*|Sh!Uҕ6O>* Q5zQjH|SC:VRŇṫ(ͪR2[9CjUCTfu QVOHɭ!5+!GJḣhJR#2r}HMl)|!u1*ĭh2|4o;Lh.9$ie6DhɕţCieNs9!a!3*V R utKh?ߠa eLZB!tM'q86e`mT/Ji RRvœ8uL ɲ^`۳)9x<QPv(Zl7:*=0&`*D 6D QV5K"0Q)4}|ּ2B ς#{rEaUS=NaL%$]OOMP(oC0|J(]Ty,&o(_C_RֳI0 Saȧ짷(cB_S0y;6JCk3 Ak$J g$)i[S5 9]-3QyIn?Li#0^6"g=90N"Kh?ef U#(}3 |J A{A=BRH]@q#E WPgA U}@ eJ@K)H! Ri<-'-5XA 1JK"TPCnҒP ~ȍX2;QK*H"7vTM/Z&s("74ȍb2X*r!_H&"744ȍf2 5.rLQ܈&Ә#74sȍh2}2r Cܸ&!8#7t4ȍm2F5r 7V&ñx#G5ȑq2:r .DI'#W։Eڱ+D+i9O#d\9ROI#dPZs Xni]'7iE/4D*܍z21ō{<0ژ˾ڕzIOp3]E1T яjV2ڃ-p uyGpodl`d/e>v 9]Aݩ6ٞGÃT{1H QlH':iуŏpa'D:__CEmkBT~UxIl[UљSN<ęN^l5t|sp~J M 'CFϤu36t?8 L3):0p`!;@[*:p~{с ڙqO<9M_'>%.gRxd v,;`;0\#OFGMQO8:;>GL07Yth&0\P=ҁj:U(gJ?}S)'E?zUgSk+ܠ K3A?͆`r [t%hlŹP TRpvȃwiN 9;E?zՒG3A?saz].(TYPAn I.G%<}yI~`g.C,=jA% @ ͦ|Dsy>`K3~;E,P=aW=(}mMsh,PrO( Soh>QAʼi]}*Y`{`{>LP% t=*gSzg&relPV.負<zϤow9=3̳,`w=ȅ*,P|w@U cGO&~~('{P[_й{,Y{z7G9J<͉5`uճ&D~fGm6\TP<1lrꎰTY~?U w鎰gv6ި 36T=҃3ߏrzW&bzFƚ3a'.TkD~HS*~'NPw|`恜 y>h36<}}Q~Nl9V`L]A-DA*w|yp}(s v} 7:|;V }nZ667L/fuy~Y9'PU]ֈ>Gz]/xFtwL>\H~d4P>yC=.8䍎q3Tt!~gF].h ڻf^'‹俔k*9Q⼠F>rBlp!&6(Vȅ< v䃡*/Dx _"#͍.NsC|l`޷4/<g|Vo7u CٯƦHU_b #oROF՝P{!zO+6?d{C(#Nܢ\y*FAyTx!zݯfG|Oe#{[ b1?VTv>}tdd5P(%Id}=l mkBT~xyl\WZ@ @4nN۞qlwRسx8$jҔ4mbR(X ,jDmhZ -B[Z97~N͌W߼,G>KŎ;:Lkhc#nhUxjZ\XKK*:ZXOKLZibyuAM* \TrPIJꝴRQeА&Eic*S7^kS5_gsvf>nQntղ ڃA mô +\}DS .A;!;HL Ot7oNU`]X?G.Ƈ4'Dy&vnzv1 V:+ lJ.c:)^Ljg |C w;rѵUʅqMF^H,.$}ҝŬ82oۃTJl0mEhmc3-\FU/Rk/dԉl>ɉ4/W[;NC}A TӚ&IG CV'*u1͔KX[KKR>4SY+37dۉ#6P[CK*LPZD./2b+6'ORjd5еf kϒ#fub&/rӑY~-vR-^vJىgrA\QV_Gg^ds#Տ OfqE,Tĉ@^jF囷nrR5 /[QƳNJna8JvJbEn j$m*g\_[TuUG]HaT/8Ƨs0h?JrlF[6sl*yDz tęu-v0?f{1@[;_6/'N~ >~-`v?PNwy) nn?!a tR4q3 K>}9|{}ʰ9̯O{B=I"aQں^|y,{p5=bLy3 <(towpܛ@ArV cC;(lr\{3cNex A1[uo3/ЖŃΝG\85z6̣,BT )1S%3>I)K5-eh9!Jև@{:g}fMɳ_x sy!3  ]w9ua?}3aeo=S:as߱5"=(=W+^☯cz/3?)ʳIi"ҧf F.zaw9Bζ`s#:7<}n_jVdƔ3N?9j1%a#gB9cZ[;UȎ1GiwLZoرc L9}1PjEO5#h̛67 y޽}޽wv!_֛8`LH7ș[rcJ=[ZsEnuA7!g {>/=>^}+y⼮2kdz=A ٯ7}K%7l|uNNş^{\hme#ӡywtxPϞf/$_ oy u!4_H)=GtkN#T.1|9HêGT3K7#}).'oħi#a;sc* sBf~uؿvͿf0@pCzP5ɄEL"9E1tp~QhZ#I,h}yI(̄]= 9%U'qV瘿x<tErK:P}F{P|?STɌ2/1_sܧ"{y=SO=cqI9 <뺟fX3/9Oaf-nDb0{;u*{SIs{WtZDDPW-s 3mkBT~uxilTW]ը*%@Xl}5J#$0Yx4s9_s:_Al)8r9@Ɛ!w!ېuH$]|͋2Ɗxy+r y9n f]GchڇC?-}g = е.y3H[Г0G(P.9D3rY)7~|yDČ.D34N7=ܻ.G.S(49+x /y\O(_HS҉;T^?u%%q921M~x[;hyC_[ȋkn/N 1Ҏ ) KYc1Fn֡C3D]b =҉剋_>rzD>}XOMzǎ(4:(9#ڐr9x 8WNt>"z5'@9L{iS~xq!io /9ybR,m{/>H <0Rܴ]PǶa#KYC轆eEV|y tAylIsL0Bgx07IkSs :y&{PC*\JS4m@άDn֒ȺY9SߠgqENځfU47 ?хKS^8$K{ΝYuso#-=by KG RO:A3}F ^Q?ҜyY/<%g~߭:vg:_?%\h#pA CsHÅ'9O* 'cR!i?/bOI?ʜЍv΍sRʟB=i X9ϐfTY'(^ 95{_+pMsjrY'Й9/Hʟэ͛:F{4[儿95qQuh9 IWE{?=l}9yܫh]PLnTh.rTӞeY/ީǧ%ρ5ZO2ɃΥ'Сz@?-$)L$9;xKk_4C<pk], Sè)Rm}0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 èbGlvvOjw7 PTEPWֻ|zO}G`hLԒ4*AFEY7;@#d2XUXNPj]p>tk( ML*7*2&2H--P3jjZ>zA,НY+~`&Z:{0;R %;'zM[R!%31|)+Ԡ#?[R2`E~.CQVr\t7s,KV8,1cOWGyKL;VBnMWvCcʟ|`YAIaX.R+)t" wŲLF-Ybm)e9Ybm1fM(s[sHRdCLybH,7bX퀵nܶ#-*JV%zCȃ%6p&+?BpDX9A_jfh><.BXY` BXQfE;;b c1FHpD‰'0 Sdpkeq$UׁBY$;_tG^:b儣n-5EEu[,,7N*LXU]2+MɑnD"\^ON,ŵcua{l> }cǕ5^Vה*%W" #ԍX^#"n'jLi9} ,ʀUPa {(mו%5kI&7B3vTKHYi&A}B2UَX|M4?0 \aԟ1'`vȩ^)]^<|w1HJmkRm[0F~ #}j&AM=AczV;|qwrP9`DC Bx Θ0Cڄ1Ux?`ƀXHs;/|HNbń>&0h1@k2ZY#9GغR6Gb= |7   1@kHxy@mJ ?m¤`b8&BĝY o_0x}rW^瑓 N bj].c6glVi] ' )ti;S8п^o~=Kh4a.g z0_jgoVϐCDQ_,x_0=xKx_7pY{|pƂ~1N'8oh^_ѣiH4l=#^sT1߸r{]*;}WTP=/|cL_pY.KD9`M<0l`K)ν.G! ڒx]^/^/gH} P""{H#=xfgyʭgrN9p%Ar)o8爆R{\d>gh]9|=#2 &LNpɶSjQy!cug=@w/:_u;wS̋M]&Xfc{n9f[|r_7j=1@?7@I{]sGUŻHs"c{+Ls2xq/l`N`y ͢kO}vRc 6Kf RtsV{KTQ;xEpvM| ` u7'y\]ٿ{e2UP` ϶} >kWos/.DðIZ,H^-K9X-ǽ cm,$> B3B5e/x;vsyh}*ST_A(Cs5}߂k÷N'-R[ouO:@ @ @ @ qpTz; P@}p Gt -_.tAXŢpX_eW%k/4UH"Go`> Yg ,/-] A`>XYU?|K |[Ot.'D#$IQ002Jga~8!YkBѡ$&E0^{%s+bO4)=O.w>sEhFszBޠ$MC zŞS iwWOJϱͼVo6)%ӰS-o%p6+l>LF>ko7s=0 *1v=S  ~?kxMw?:E p^03lV xcĀTCcλf I쓸=Z| g<ٶ| .kk׷۬PtbUEkgI[LrjRL0>/ 7F>^oBmca7}c'qWY?g}_0L0lơxgbӸO'p;uY{?\K1έlɳZf]<~7N(Ga6toB1wkl]`[ olB.ѷiS6%jќnGU*4O`J"|3'æGEm?ε~74kW߫`ȝWm+BDl@z|goYI8$[ѿAoqW$FgPU0}Jåбޗx OoXk:?"vv=Y*x&Z{>W聨ZU0}r "֟o|^XfS?8w3*>+3;?|%{L h{/ ޕ?C!y80+8<*Iݽ Wn]!v~#\Էi%z.#hEuE$RŠTteҐmtaϙxբKsF,AVgA255<kary{{w^pYH3>>~sm+vuՑνYu8uN?WP>1JsWiV_uKEϸ/rˆ_gKW]ױEYcl,[TYHT}xL#}A GV7^}>iҞ-i;}LJX&TP3T#ߨgJl e'=?͘ona|7>?ǐU%;/mN/IfQփz{G}?v✽3X~j{zTAO^ʰ>?sy|G)PlX(F9/L!1赴wuS-A#:|ޙql >4`cDekT͏GLseɞ~}%Ȭ}߾}c>4=BLaZb-xD==[][2r^ױ*2lla? a |h1s$81#/9ZpqF\-O-Ѡ{;zOܓg*]\뛛  9@.BKUxe6anBN /3`\Ng*ZCsСY<."H6whx1b\ cM9, yB-zjp vxrb>dvnlgXøc8VkUKM)C4?7:ROr{nø~7pr-)[F? xl :;K=8 xG9Α[#ncq\[YoWn+<> K({2#r>fK 8SVm ~{eѥTўR-FO>zXr~q(xN#>v1GHcbU3i q0昔χ,ƳxcqqόYcfϔbmD_/Yϰ\CL>}.C-&ܳ|9q 86?-_5#+&dcއ|2h/y~sN?~J kbhqX*rEg-ۀlB<;eg61o t.[}~߾Jd?}F=kk)x^#OaDQ/px3e \E3xk[]Ao ߆\Z%#|smr ?}[ ^$UڦLmj 2Ez?}w~1Pj6ejTKƀgWSkndjTK6s?:eُ_4#?JO^+'hՠ)SۧZ7dE]_ j2}%촔/@7 )7d)[w'y{> kndjTKY {*UϿp1שM>d&YGt':A1 \65m sH@1 LjP۔S-Af(di)?l3AmSOhO/6`ԫAmSOl+/~ևq`dH j2}%l[g:mTr}K>d5Zwe:AmSOUv86ejTK?Fjhv ҹlk x7>5l7tnKߎal%dꏰyxwG:}t +"1g\Wz ?47G!~(8fǥ/w&o罹 > ײWt6 fhC/2ÃO{G7@~3G EXp #J _\=x " [P^b_u;v ȵ[A \ T,$q\&|cjG~yG*M+P KtC/ζ5b |6‡fy@fIMQme y|y@mj k|@u^ئg:#\ >P됋s;k0ۂys2Ew:lۯB\=2=8~STᷜB{9;+gH Kt3b_6IuvlYnh gq,B %J痐 B-G&8_pu ^};O$y-:=@E5;O@,R1GoA-NKNGNbYW6 $R}%-8fcFcL $63/C|>ZIJ;\?(hi29=86Pmj -h_1}%o `?:PjTKhA3u@s9@B8zTТvt[x/䀳UWۧZBV5H [ud_A]*}%=`ѝ9޶5L\uj -j_{g>a݁k@)thТv͌0kk@!7lDW>jTKhQu ~5Au Т6oЕi=ZԾclRX<µovb!9-~}%=s_?{]:lOvVۧAe4qi믃nOE+ŜBp?aG5 [Z_FKz~Zu̻ (!>'ޞ+ןہb`~s:-W\wלo1TC' ˇ9o~zOt֞ k k\;w( kp:3gVMHG>ChsO8sd^~ c*ݺ,{.` jԣ.s]cj:?4ʢXuxWTwh)K4P޲q'itAK0_(iPwMEEc^϶Z9&uWkEW?l=3N¾ZԁbmtH > /B_ƿvHAo.B`No27>q-sJIIj͊x"5ɵ^(W}5s_$WNQJYoɜR2<Ydu}MVi{ & ^c}&±&SwBΦ"+@)wYܓcb`: oh{8_)sK*/B?`u߇pё_vfJΚAA;g!ǿǹ f}9}%`'Go?@Vv'1@~qExQxrJ zu_z::> sI{*.U]??y`qxbhŦO)(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((v6dLmkBTGxih\UUpCQAE>VS7o7L2IL$MۤvնADq""ZM[ۄ֥MZKKxwj:]I&3s>̏{9s?+禮Un4lgg$b~Df\_ar[7PpX/Ӵ$y?9FCF7/<ҳ֍&si~3LC¿.mpv ++q_I>GpԵGXr|kEOvk8bF"u* /}|ùΕhro3{HWƭ;yײ}E̗&%7qs}Z%ܼ7w5ޯ-A` y§}5Ơԍ}ub1lo|2 Ҿ$Q߷FCa!7giY^}?iX^g]C¿|[a}Wm/J\ gƈN0#=$'9i7k| g зM6va5W=$'־7g;ױ«{r@ܭ0=gM56A9ۙ}/+AN$jaZX{H mKT/B?6?ec?ި]Q7H̟ +{Hn"ӆo>j<;~٪2{|w 3{H+yCHPtˑpˊgz N{;ޜe;{ +sPDO0]C~b8=GSa_phl;¾|})T*v'4\:oVa?5?3r6I]oz ,,}/ӓEGخ[BO_~mb0Ln<)_~x} rUo?~¾r##;gq^Ma_aˢ;_YZU_c޻c1Qu\}~}][w$ºSjz3Z9L(}uTKMk\1C}fkZoF7>Ɨ*z7k3#wI?g:7PXw+;Q^ F̟:~m5qB;8oTc[?_yL Ly}|1[C3?G=f1N]k$JW"Kn]ɗ {qfz3úO7HL *˴z#N!Go G9=9")k*u'\;ՌQ3;[&M?:ӏ6oL6 \Ԛ;81J0g `i>T^^,.aLMys\b<ܛ@HWߌ|u70чU~dя%|0Xu_D@Ԝ&v·K{Okwu/&z뤖NGNy_U tG;}U9JT¿?GTe %yK-[Ÿ?o %yK-[Ÿ?o %yK-[Ÿ?o %yK-[Ÿ?o U_,,{jGވr mkBTGuxw{?Fn.޹M,,Y$q=|׹bRh ?', f Au}-j5H{\n@hIԂ*@3y1d kK~ _]T1ЄJbͦkW_kw ƟlK~/%1`^+/O!e|@݆Wү]i|E Hz\Иy!yCgkW_eH;>t>@_Z*ڕϷߩ<dV!*ڕ׮X+_~ʎ9-/Kvu{tǃs~y`v}U?!}C{[b?kW_k7v.1zyS+ u?~ ڕW$1X\kW_ҁje_ Ğ6c_?@O*| BZbkXHuͦlV}èz|,xYEjS)h]yׄ{3, #,a:Ɋo\,Ad."T^-$?vM[;T(X@E4sJGa*a&/~ul(!NbyO뿎AS}6ҟ$ y.3$hITH,73MS>d{+ҟߤk!y`ܑc*D ~2w}=K:xj;7*M׿&] ?g=#~+ R}/\{IW-Om}&igDKz~(s5H7lgOPq\8%qAVUXƉ7{jfqMj8^0J h6?c,Ԫ@߳ 8Ou?-}Fyc^gXNǘtiZS1Kf'IRBx9_ׄDcA?KR͜Ғ_X ?QGf+P]X9}퍏LF8q`\γ7l`Ѡuڃ`*p ѯy@15 &snM- ):g v_=;+HrT$y_%cAʿY8؁đK,PY;/oQ]7<|{Xr>1V+h-Q[wKU!\CDn^'c+C3#[[V¹{->X&ea.T pODSq)^oAkUIIIIIIIIIIIIIIIIIIIIIIIIIIi5l{w?w{N}7o'ga.'J|" B0j v^%>ж=4EcH'ᓹ)i] 3TBAg5WZsBhw;"j>3Gdp>W::7{)KxsN$Rg'U_6)7S@A3qz^L(L_ `w&{;}&(|f!W==&M7'NgscgE:L3~ `uFdrڡ5!=㞍ӺS,3CIit-$Zq9Rc2g9 :Q'Fwt,Bl1=Ǟ2װlS 940֒,}k}uFYY]C^p&ޔ70\> Vn>~JÅXl߈w LI4pO]^`߷_겏WQk?SmՇ/dߟ7}{1_jY=*wLE°N[l߲jw>ՠeߧXmMg뼉l΋oN*(~T4~3X5oڠ_X*9HW0xcg?;wwU*·\O}CN$K_%q}wOGKj? Ŭ{Uex4[;i5id֓o1k=45htwY=+5X'c>~1_ґY<0 Xr$[nKo%]U =>ޯz1 u7[pɷ~߾?,c~Ο,u_UB}W:}qO9/O^\Qb>)7F}ȾӮ=u+u>o s}?B Db|O<\( .w6OV ksM~E~c/HQ?JWwu_}-b$yYJ74Lpn9[q-Ǽ%96<%#={ss5WjcʥM㴩s}x;9m{{X' ~I]y)y&]=3wucz]b? OgrZY x:#{ ;c?\WKҟiѕ;_wMx S{ǡFy w{9߅HStaƎgs҅>({߷~Ξ/=_gܦuFfOZb}|{[|eM}?Be=}|}ð7;c?~'Q5>B"*}X}ػ{en?NMZXu`~Tns?.ҟI=Zf溞58Gc?bo@7Npqq[ 58Epss?۠Kn.앃=speQլŗxO/?IWwAM߳>d8yB}z9?u晿sy^h]]C}jԨd?I.L,yCޭށܸ)YN,{7|9ssẳ/Ϙ1Z$Wp֯tS;zk +8=qGy_ޒk%z<׃?CBk.:77:zlnðU_/}<ܱmEyC!|~S?~$]w]m^P{oh7{t߯p(bϽ [yk1쿏wp۷*oHtOP]F{ڼ/΅Y\ey_?E~|mg|g q{oiO4܋5ۑ;s'Si'.vv㸮Ğ[3s*'oɻ?+2߄TMX~&̃Z98ܰua4wڜ$gq^³ݽts+7̢DB:M}h}p) EK耵ŷ̃6F wπCsk򽧙} [_glk:f?󻸶Fems ϝ ޏR׿MfX`~:`-mݸ;}l~D0|[-Hx2>y. [Z)k1uV vmn{x1xޢ~)Xu˭}n378M//.!^Vn츛MwÜc/yw,r~g(y :>_y{ 7œ缠c3}ΟUŵ]mg\|3Z_ㄾΡ{a..r>49B6.$z0<=umqۜjNj{{kW.2O`fuF=:SV'œw:/-jz{kEz'S\} %9_o9;wg\y{sǜ{<:~cR9_9o}.z_ =o.O-ĽV ˜o[`r~n}Owjق^/o/yAwR{cN`y쩅gͽcNP}3zv.x[/yٮqNa|U=|D{^oi7!mk}#sV︯꽮{os00qkg^2zYro=Mz/㮂n}{wy'oa17v.G bS%Žަ7;S㮂?f{<x|.Oi9 Kڙ%uU^yoyf/'y9Oy[Lˏ["g5{$1WEۯ\Wiy"'M^߉}Yk{ZW|oz ͨ= >wk򘫢Obϱ7w}^3r+?ߗ5)G|iogtYu j.uq; y3w;|ot%>O}UB׫;IO{!YN}㝽|G349<xoC^ew݉7Fg 9AۯZ4۩.^~/g=] f n޸Q=~v!JwU3>_=fܜ[G1WE ;1ڌvqiGϾ9eyU{NyvbwQ[ԅ=+25'ܽn}Ke{{$긫y<;g~4؋\-=xmpΓN7y].b08|;7${nbo|=|߭wlqc]]?73 㮊hXz~¾Sy>Ĩ]G/;Fn_X|*2-Tٯ7c®f=Or_\7e~;2~1Şv6s]^~#=4ػ}d;ڲۺ/Oy}/oZEioTs_r&}L{Ǻ_=!b1n%{>yݷQwb RL1VY~mϣbo kA;9=~FE7𽽅=ضD>LgqjXĞ{;ǟ3J:zc 粰u랿(|N3KV,=g>};1m%Yw='buejz]>ћKl5)/}q][$E/2?NVk32y,[QJS4vW}קn;~}=8L{~Erk.nhOk,y[Uvg|*侉κ eG'ᯘR1yOB}!y [YJ得ޟIim >r_xǯ.7}yZ]_Ur߾sgl_$_KOŵ}cw8; ]l pr)SEMT¹`4ԾE7ח(!h:spy㟄H!nj罕ޏw<;}l_}7xg=/!_DS>/)?g~}/|XFS1}?o{:{?ýp&y$˽z&y$eߓOR>J3?Igmy&jn?{7G xMj+{ߡR𮇯{抐 wF'apn;?g`<^~je>/{8GcGnG4y(JngFmh| w><}G+?Uo~""w^L+?JUƻcG cG ` Sʏjk{ocCV1mkBTxiTEG"M13(vޛys;;{, EQQh#xhDX%"B WovefK{twUWWO B+օB>3PV<Q;?s;/@^J׍[׉rh?:*K< @h le]KUp3/6b^ǼP^n?zո l A<`%Kr>OP<0j3DKu :@x782탒 G 6b>U}%H10Ή81a4+菜ڿH@(QW~&3 [qeF*1۷^0n֍fflaz =_v"'?5c8^$0#xٮ Q z$/  y&/0 y`4n}5C{XV{c`#xFK zʎ Cۜ`4SV yAB|n1n5Q ~0#-T/A82iX;G'? = m4 fω8A+ժ}5c51e>;cܚ4@7 b ą<'փ@Oh sı ;WGέwR\8ąv>[N Wcb}sܚ܈q!eo[np ̧ :7ϊsns).܌u8WY!?,bpMڿbc9hwJ}6._7`0]knJm5 {-qڭ\Cc`X牭Du-1$` 4}v10=g2g?1_/p~cܾ 7<0tbs|7AcEc]!+Oq!s%H]sNw8B+YP, }4p `Oi_ ]lA{,s|J 7Y=̅3`黧Q=Asj}tNw9#l-c"Է:cq0֗Ny|Det8>FuW/'IU=d\A}U88ɫ\^TwW^e(icSl;\ c:#|g!AO>w_$Ki.ڢU3'? I7tr[5XkmE/ar\ ?K l% քpwS{_|MB`D@3UAAAAAAAAAAAAAAAAG)%&&&&<J7R-mkBTBx]W{%ld啴朳ec9H8G9Ƥ&&n];'h%U< 6ѩSU=֯IjVaâ]VEX`JKiwpgُqġ*K/sP{/sPSyL z,\K g8s + bEssgϑ*Ad5cF'=\1g!V Z4-x'|-¿2ĿiŸ[p ZUnt}|-_|$/;x <_A F<_A %w8SHJ% X7B i|-_27$Ygn7 "YB-%z] sP jFF 5^f0O 28Gu8y J]G-(h\Fvx.~@ 9R=fA 9BރAb6ڜp5|.0{|t9M?79vK怱v 6P.A(e_s爨_>ZqtAxlj.z}(_ R+CZ"ϡC79R +ycU 7T`{@[6y5 XIX*KK?=SI<Rv.@w&׀VX_D΍j@x7+aĻzHO֢o4}8<Հ<~ B^τa sݽ9SPsV Wa^"بÃyK`.5 gAs;yK />=`R  <[TRm¹9TYij@sabN40f}N_s$<ko8ϴ# pXr o?<*WwCxСD ΓZD `tҀoZOPjT Zkh@^OyU V)5@:R&ת9qS>ETBD oo69a=Xw^p w$Nb\}WExYxNTrSIq?xA~`^U/^x74`P^s@E/f 3`1f @Q: ^ j9^*û[pʤu@il/0 1Ǜ-OeD+5:8wQT yՀLbՁr >:#3)Xeu -N`>;+Zcu`ߒn_ :j|u&oTWYXPmC4\h\,b4\4X陮O""ˆ: 9{24 @لG[r7~̥Lu#N.5`MsA:3~V}:h.?+Ռz7j86;"8 wu`\k!5@ Z?\ދɱj08(f?6{x *9geΤgAIOpIx@;ATbHX"?h VJ:`M@;/8zюFgӯ@{B8ӎPX?V)CMr,]qg,=hW;8yss./@U|!~h9I C(8p<L 9`#Z$@g"?h :w}_70?p8z  8PFH }|&9pyx,XTf7D^8L==ޫK%xB v~=컣_w8`7 j͇gGxg<``Ȱ32{mNZո/HjlN*^|/~bc9|x`+7xC=iޱ0N@%xOT`7i}S]=F_ &awW>}qfAa>?[;uq=p 6p M`:_w>1fWy[yD#Z`65Hy 9B]=0c-]Mȃ38j ػ2߱yn ?pQMp2OknZ $/݀_DqjK;T d5 8`-|&yﴡ,e3)s=Dwetcえ Lz& gN|ˌ1]@N,1/`?+6ێZ`W&x' `: Ɖ&h\x/rb:s?oVnC| W/.äW{C& zQOğ٪ #.uEl b!5 !/🽳bz 8?bQM Fkmp xPPly0L&}NoCfdUr<x"?FCl9@7zքpI xDoj f]kÈ}rB5?@wVO^,׻gI@XjjgiЃ`0 `0 ?ϟ|:seQ3|ӧO|:2|.};7eGFO6_Qv]T]^ˮg{>pjzkuo{yye?{-x/ D:3D&򈼹e^Hyi#/OGzϪ߯_~ :sMe#M3Y#=2 QЙ[\s=E8}E>GȩT ڲTg-}VfoSVwzV}./>~!?U1<#}=F[ ~QڋBN..+푹^edLo+[\-k dW(}6q$#?z6Bөi?L7!3O_Q}Пuo[=tkȋM!'}/Ƈdr2_Cﲨ: `0 :8o=+8-4}۞cĥXdq{bUq©ήm!ƶg*ΪU\z[GA=^+ru{LV U?)V>ғ)x|Yҁgi\yi^cUo*= !TY?rfgWsʽVn*VX#=Fϫ+[F~yH\L~[O҇h5ݵTow|Sfӟ+);F;:x )/OS yUo2e)Ve3'wgGg=J^`0  ľu kU,Ksؑ5nY,bXw{ w&3QהNQev ]ƷgcH˞i{A3I8hwduwUIWq8I>+@pQşGcZ\ƪUߝ]/:3d;ɫ:gB9R|GW~w2;fzt|+i5nΟgZY|<1NyŬ|E7k?z/k><=Α}N΅>uWydʬdz `0 *\?W8GY:Dgcg< 2+'W6qn؟{ru"wU쏘~c#T?+y{Q,,^qF/Xv8.֩g3}ȸOP ~n%hUG4(_sn|W}Tg&x^c,Fѭ+ <#+}/Uw8BRh_|33!mr\7U9m({ѝpvew[xG]߱?g;,nҽow8]וb?OV=Z_#ve?vN_WrYLo;1g9pV^G~>[_vNOS3 `0Q[ veO\k^8֔v<Zbz\Opbn$~}oz3ј mK vU]^iNWA#x딫jt q :E= z%օq)CcYEqyRG-+u (K\hP'*^ء^q=m=y|Kvūe\rȊ4={W1;=ݷxp;o@>ȘT\Ԏ+C=*ɫ|GJOCW]x1.ﵠ9_Eб Vq)v(ʑ}[GwǺ{-oSdו_˞׃2;iT&w*w:g׭SOsj%Z[~_˯d֮+w]7 `0]kIu+eL]ւoA^;=GR?v쯱;<y o$N1紈=:ߥPVu< <&3KyC/4r)i=*/|Ύ^]QNН1qGw>ù{ ?Kv:A}E:_n+{u=rq͓̳]>>d}+|L01`0 leg:׺񶊝`W,3O?]\9P~[kOWiGc~)-<w.3q}'vuw$Vnv(r52S;Wk_Kϔ8B/hEՠ'9w?K;x:x<|@cϽVyc@ۖSw8Bq]=2lBe6V}eR( VeZT4ade2ޒ+nYBTqSߔ<[&=f[|szP)G}{Zׅ3n7jpWwftEw[ǽ;`l? `0 `0 `{~i`oLy>uoi\qK|}7Svu9G쯿c¾#>,jow{ՆݲL=mW2u_8دjo?kD߱mw>#}E:OۡO;y`$j?tUmkBTxYh\U\@'}EmdMҤ&im]65iT ZqwQڤI[*vR l,3{l=VB3K/1[_o1[_o1[_o1[_o1[_o1[_o1[_o1[_o15-H rk4ľ|g@M8f44߈Fr7 Ax5ŠVbaȱ3r| |ȿugM# +6@sƂwq¥u"[L82 ; B#א/CfqH;V",'yȴcC>||cA(otba.ò1|M#NsmX$DuaIPLZ/F SSƧEi'vFI 7T +A ~$7O]`Ak eM+@9l5c#uV D~| l˺c(_.T9Ƃ,'>d/~=y>t΁?zD,[։Sm8jg'o,t|P=TbcJ|\޼RDKj0`>n&_WgqBwk5"Y$ h,mV5D8P!O.ʴ>|0v}gEV3h}sq"kF|xK ]ى?&W 8sHN25AubL>30w*[TMWrF?R5jAO@UlnMV+ #} ;9K{ .$_ N2k|Rօت1kZrekpR PA g #=K~6~Rc_?\+Ϗh oP#Z>GΟ[dew8DC)y|9g`;7}"S.ׇ4xKyLw[93Wå OȻexJP*n LA-\%ƛT>IuE>"_feO @SOȷsX>ˉ>|v85?\ٺZD)͉;`.30(8ԄprIs~g [ #Xք}BWs+a?:7300h{^Я2p1gU8/}G{RF҆c:{tό}gҠ{_Ù#rF̟bc(t'Df@7^e # o\hG#}|_uҕ?\ۿIvsy^uaOf]E,7 ԀzoV5{S# 0Q7\}qwao#fd?ے܏߸?d'"|gEm_/VByVdȾt1NWu`Ms {-Ė`[Z`=hTR|{CO?яA j(ٺ^-~t߄9"sx3 ԍުZ`aٛð/gy^z?R3J=sڠfOuD<[y`?3wam$i毗ыЊ˷3Z Z,'R-3O}߷=7H`ȾChM 3ٻK+T{xO4eh0^ ʝ!co07<@\~ϟqF$!x92fqpaX_ߘX_Zlyka,4kΉuO՘RXMO毗c飪{~cp1}Kv3^NUb݇WW:Lz;kum7Gy޷+z}*}8^r?gsuSWMscWrmkSyޕ޻;e{,̽¿\8x,+]iާS]r)Ȼg_zW3zm6mh9o0i>R6]eeDZd.FXOwO漹W{kVB^p-1-(՗WT|P|#߯ZzoW|g>F,hpL+L-*$6iֶ Wtw9.r}U>3u{yu>:8w|DP@YENwXacn!&=J?P[^YMJnT}pf,ӫ!ݹn.»\r^Uzَ=ϝ^=b-[РYE<;"DN 7XX8nbө ]B<޳rtjf=]/aw:+pskg\uޛ]>g{K}FF_[ߊ=`&q `5u16/91DQwX_V++O\mK QocG1#{3.;ȹ.ػ{rZG .ϙ]k~1/]E?>_[oqq B v]k(wc"b{y䡈uԊ^B\_"g /o7q5 ^GN#E:xqass=/!/ֽzǻ7}uПhb}#@ &`\u5GHq>8]ݗsJQ]vn^{ӽM3y$Z¹9bp,صa->1aƅco;poٵҷ7g7v9jZob_@=r(+qbAkN,ǃ]k#\fUZϹz]>Bܻ|{sܛg:/p}֘g:gPKs@ N,XsSxcbzq Nrlrǵ^߹l`A9ƀy!mθ=co{ D}9J9Ơ7W#ܱa3mʼn k9i6r\\Y]>!;sk+y.9A[_`lƃμ!ņ+FXqfָ}\Vߪy%74 'x(|/{ͻVW9~ oOW{ xpN5'1[\qt-{%{)jgZ҉ ;7o̧[8K^Y|2^'[Gz1΂OĵbS.s5q+mދD_B޳prdvGؓ}LsMV;}|p.\>1)ҵ-wW0`gsc8$]Q?g+"7svmx_D ןڶ{L1;8/ydFg`x}%|^p*ܮ9,XkxkuLpm8NT7e[>zDm"3}| E8aVom"+v,}vOYDsl݊ k+_A\Q5!LW < 5γ5_m~ qd/"ttbBVp*EMˢ|L p<]\_\rnLU/$|\E2YkO{$9&"ټ8a2sAU."ѴS5%ZF_f'pB3, I\3kK X|eH'2/Gmgf.[UC/hn6fC~F5K81#tg_%tj_`-i.&x8:TE>dZq/}x׉~GYpr^p>ˢ6Cgo5Eぇ:  ρ/yQXzr?ˢn&iɵ!/L[3Csğ#ߍLY8'M\:5I?2;E{&[x@o46ycoׇ?o7 0A A/oM9g¿,^|m/< pbAjpX'"u!NmM4zO/ VtXL$_|}'esfe8[UՏQ-a:̠_ q>kC,AtAW>U>8,3xV Oj}KKFZ_Y𻂾pА[8b>+@9?C^yn#/h,B/ b'ݷF:Q5-V4++3A +ʳ?ЂY8 'Ttÿ,nM؛ K pޏ%nfEq.E,75Ya)E^h!ˢtAQ W6n=UsY5rˢ{rYp=?nvg 8 <+_p_P8ȹ;_xs_E$傹/LYqM7+en0'eD; *o AwU_~;pik A_Lwg_l s,x6[̳_wn_8 N-ةߍq `h+/ '<'^E8P4S_Lܿf{ak/ 罸`ND© p hVˁ ^ e馟^yX\ ,&1[_d9S\~eJy / ׭w&4EA/З3zL;̫,o932/|Щ^"ѴelY3!&o.8ˡfzO :en]`_ȴ:,??y#z)">>W_Ͱ!Gk _*ˣ wG /S%dCG mkBT~x흍8 FSHI!)$FRHnw HYx3ꇤsaaaaxIǏ'U{o_ھgW9 o'GW {>~Jlo߾)*/N\ϱov[iZ_ձaJΝ/:6O- 92b?Tlk%?_21B sY5>:>c=1Ow y^- ڶ,XzusM#גU]>H_yYv!ۉ_mi Rus]Xm_g)YY)m]y,m z1aaaxEߓGקo/Y\k6xjgH|yu.\aæM&wk#ϐ$?]Mo\Ⱦ,/ڥQ@~6s?)}, l gX #vQg Bٙ^uのuhm?}{].~}v_J;xogJY]޳@.)oqC?}>@Xߘ'-(W? źvƔOʙRv[K?[A}?-wmՑ}g\=c}M ggg DŽ-B^k_g?F? v0||؎=ǧHPgs/hؑI t~{n^}ZyD5XWvO)"c0vY Z|~_%/,p\ɹyΰZ/;/xs_9?Pܯ5ݻ\[y|č8gʱL{? 0 0 _k3>z_\S |<)b|7aaaxn.ta?l^Cvkؽ#~e)3<3^kdlc&jK+o"e<.ʞ`^(3zu l+6v<ï k7]/lc[`On}򚄫 G뎱zt^v2)?;Wmr5ocIz?Ozx{&!ez."ѯ 1Gg{+ҏlw<=}GݽFƨ^)zIpG K֜{{e G12ۭqiumf>.}~a? 0 0 [u+7Svq֭y΅ ?ނ}XwŶv?ߩDZۓ-q/?߳=<~#>Fk"qzrQo 9r,nY[;o:)@-`ק-7({߯S@µK9֠ɸ>:n3 _[_*mtcmC>qSL=<6;ǫsaaa{xˌ\ފpx?0׋#5zяc]x^l򼠕(f:~٣^lin59W~\;?vn6erUbS~v^U O7O(|;+SG4|?f*?rW~2oNٟS9~daևmH6mX[J~s.ym4ٶO|Bd/b5ɿyU? 0 0 0 0 0 0 0.P~*1@G\⟿KrKXs2(ߥ纎J8'>X@▼QQbqwx b)_K|v 1M6kee-2Ǜ59?K^E~9ϱQﱮYF8N?~;:=J<-tĒyNAgC \NXKs)'^Kg\~2}6}Գ)n]Or^j~"{p29w6/.z-v:+M{WJYZ굢`% Ҥl9ힶկ#OUz+U?;sd~vND7*.Y+v:ye;8}~|+ÑޅN9}{Bƞ#txխsXɿkSV/uJ=o G<ջL'L:D]6jfgLz/+ؽ[{rCMYq~[{yy czA;w9zszWHVax3 ףmkBTx]YlWvj]%I&׉on8iCT )B x!$*BBjB&P";q!i91iu4bZ$x̝b;>y9˜ -B -hVS[7vfos락'CsMhTVךkjto'IgXS#͛ʽ[GTVf=u 'b'/TJ$3|w0b7k.տ?4o xkO o/:XOS#9)}o[vlQ쭻xWt R&7Z؉&}n*_hlذa^^r${ZŤȿ߮57b= 47Rt Mmun\>MrY tiC;Uܩ̞'>~ {D>}oo÷XT(gK-v>hCk˃-"hv[ &qοy3%\&ŵۇkBy _HW!yE1Sx=N6u9Xr~R[F Յ_??mLWֆgCrQdzO~rs.}r׷4rEsiOK}w;9'&0a1؇7= ;E 9ux sĒa0#޶xbj$w{i="ng8z{w ?66?O&nCq]猻=Ǔ.sx']=TJ}7:Wp1-=wwEnhbna~wmS]aAųZfZۙu3[;Z Ldqi 4(X0w&g|J'6qNY_w{>:~~w`hj,JM[>Os 1^ߗ;0O瀣o? {"<XW>٘zr:^Syx73}^=s{wqPaYy|l_u<gymPqGۢ6WIh{myK\oىZڞyFS1^y{MmOy7wbWi8q{2vr|oMmv1^QxU_g"o`ޤWmܘz|ޥ/'s˓ФW`}Uܞd^m/y;x)b=_߾=:4U[OmCspX?fvdG}ov>%.}Σr;Sc^wg k{ö5\2:o /1| >ow? {q~*S*ttu>w?8KZ-=VW [Mmv;` pbj|N^+yT_pǞzHcַ$*>|pNJz&֯;|G{L778\y{kihmV}~<`I~{yv0 9_7|c:2kD~Wz<Gytn MKjBW>ya]O7'pZ#{9Vm; wbot-Ө>wرDѿa?N>[1W>e}0gt=pW%1{~Q}~Ɔ[$=y o`:#3zgfa͡C;6 Ly5ApK4dmǏ3zAQ{<ǹWW{;g|& 3}5L9> |}27c>xX5msCkZ/{y^evîVس;>{ἦn_edZ!{VMWxv26쇑Nz؋Z`/?ϳ3ObL 6nb*?mв/T`Uߕz61_s}{qX7~X& 5,`o0 < ڷ{gǻ~޵:ޗk<>Qþ*owkOﳾ/}&S*~/AǑ"=>˷k_",^u|rzzwNۓ:y߻ }_Ϸţ.9y|+˳SP}~G$}#1>:`b8J{2N6Mdϙ{b_;qC;y*ZmZ쏁U}~iw7:۳+ލz|杻bwz=ElZ`od{|?_a{{nj~Ck|o7kv՘>|s]`O55ժ^} 5&z/{x0SZaQ}oev+b9֣}3{\= hu~ַ^}_BwozC{Q >kOTjl_Pk}}{})h"3ZgP}'=rcajt,Q==>7[T(Ʋ_=K>*cg{\LUU&3Iu6sw8E>Bz? Vu"p?Q't|b"HfjxZ|!]{&#4>Vs߷T_;u_q'Byw!_p|oG.ro ] -޸߯>{}f7\΄5EzXw_Nxbtxwx.}k_!~ 6!{oq{'.@޿7v gv5~U؋b?c_x? ^vKT53j9+aݧpFd~ۚϺ~a Dg׿>k|p}SgOpՎĩ$cg}!NvGc[j Tx5?K+?*_؛Wm_w=}>,66pWxQ}yݏ~X]~{~{]; 'ұߡׯ=f tj TڋF5brߗ/|kAzϏ|qĞm{~cQo1PiwƺfԌ/.Oi9Cᮁ^Jtp[Li3~b7~Ch?iW0<1Pi/=?1vxO5*;>:~}+'Z][߹ε_ jTdKWyWu?FIoE6lއchJ;Yuy/}ߓ\z.iEu_ksXxε omkBTéxig<ʣԊŹ{͞@91œ3;3{,Bv$JGE,˻*&$Ԙ!! F*_ӽoW=?3}3c?g˖-rݟF eFċ6BƐ]^0rp{fA|sҏ#o E~;SȟǑH<'y9Ei>p+FN ?E~+}'u^x!{GE?>LŹNQC۠wC{EOzϙԅ4ʙsy59\qc)13o*YoJ''d>kww;lbj5՝@QɟA'~z=0Ĺw[wcN`S{M=ϐ?ɹf1h־H;.W 4D>o؆=nq}lDf䈌9m0i~vfg5<h7B;$xɄN'{>~AY휚׵ 9oys:kIZ=FnL󄮞cnK8zpڷOM4gA_=%wngiJkصO}yϸߞX7vtJsIT[DzRi~v5٩g9z2w1yUɻzJ=d{a#yٜ Pt'=]b~߱Hwճ7wrsNig󴹟' {@gɻ'p~`lڇ}z_ʘߛ}h_}]{] \ ~< vtGS|#9e3`F:9YaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaIsGB{ |mÎ[Zar-kndWT*.0 frlȫVv3C!Y=2**>-+|7HVj~VVBгxCf/zȩqUܪ*p vQyia<b"?w&ҳ0[Ex^XR9r+^xEнcsK\ *yj8.2`iy溨kjaaY ;6ջNʊU!ky"NtzMdjT) 1/uijgͱޯV/(Q4*b*ضnF6܋˖ ߔb[:cYlvl},$!5~w1 솵7!VfKvq³qU_sQיp] k6"-SI]ķٵ.]K&Njj}.S)XBa+`V1c|U>MDjlOstDn,ճڀ7hu\9:_DsOb7q^JZuN-O8L½w צ9!tDzu}7s9 <߁ήM;1ϯN}9zUioP e7w~Q7 mkBTxylǫ^RVUU#@ G(XPff/zbc{m06G!ҪRJUZRzEm6 miӐ4MS@I! B7xֳ|G^7KhFapVY_ß'9,)^J3eØUfZ12aMJ// "ŜJq`M~e+ݲQ ^s&ZZ]UBJiպjE[*x鐳ap3Gs0A)W?PT*Wg-E5&=w8]Ma|p>w{?%)\ŷv>]1c\+//;N]{zmH?!}}1R~3=2yA]=[MG'WSbU4c|j){sO}'| ;#rh}WWђՏ%+T5SlA}&0u?1nZOzdɱ-ϗc=xV]MufLdjIGSB;T:łr]\˪6(&Qxv{Nrf0gk`ZzPIg1T^6gXRV=Z0e-5@c߻\ /xu^g. *sr 4Vux03*[tX 6w?%~kL069¦Ã9楷`X?k|~ Hm?BnUoW%?<̔0=g2>cPf|L 뤾x}ƋYr#cY{Ré/=tOaY@מҽM}\0*xZ0w2w0dN+{xn{._/Ẇ&3>Lm"q567,%ٍ"3ؖ=xa×,+iu?_0}g ;hN pmX Ò!f)4yiD9kI_5ձ}&1JwO3w]dNϩ.s@?ǿ9zVfiri᛻xEVjBHmY¼?U , Ƿ܇bu8֟cDO{ܽYﭽԾ-u]^nwe2i'<ل8>.i߇rv~wV=n>'sA{OD0q{5?cWz&8,ݤ7LMns|ݾ߲+_1-Kf2N- EY̼/|'/]M~_w3Rҧ2)-f/=z:UnOM:QgdT.{q_(B?&s_tmO#odMT.,ϝԱjr6[f _?LӒ}^1]cB惷cg{Q{}ʳmzmTr󸸾Į_pC>1fNK|5^۝Ru9^Ng2;-gZ:mdz~f"Z,˒ zv-=sf>s.uLz(AB:wU)s6v(w oq)Y%rE-{mlݢ'Vگ=?Kxxgw9#ݺ7ԽNsaQ[:;q'3_zii\Kn#w:yM˂{1qcX]ֹ҇Q=K[h+ L}͋Sln4_`GuM>ݿսts?ibVZ)~/r^ G=ݝs;]wOkRige.=~'}VYjraw,z13װ9!3c35ca>`+ʘڧR>,=/˵iN/"p?`NFhmkBTЇxilcI'q}NĎOIL20@MBe>P#ARć*ڪ*D[  bTjEU6Ae2.0e&v`(n sbw}Ħ(sw%TZv 7]3m]]p{>t~w$|kڃ>?gO{3Wtwkjsip:uuuMn[[[XpWSΎtuGzD KD/c'cCF>_EO=/SN{H ߁߀m 4|XĞvnҠzI'Z?=pD=8xE`s8 &3:ճw=ї`2>9طUߛ@7sv[ mwF*e6Kɔ>!},dڎ.ed5N0y q#_mQmLf6c 㲏 ;A^OVYMl[ c4xmc t+O,uȁvql++l&ӢOɾooZ}Ig}q}QZ+FH}ؔbG4tqt/n:6tԒIyd$Ef>>خl z)ꄥmȺȀ. lB h~`?l]MYls?~Ч >( } gu҇R*mPDEI&M|ںMF/̟uͪ<<=H~+RۣGtvz5 9qC/FյF:y  +dMTC2Z,f/|Jp)On"3K/4C38 >FwQlH:/*jEB#ՅX @%h617bLs58jhEZJm#XaZIICY_"(b?io,r_W_(li.c9@U=qXM #^ jK/+8>= oy7Ѝ>X +V&H-:Ax4P?J|X '>uA V e|HDQ8;@y+ƽD=PU*7bN 5҃R@@ Ѳ]+r霣= EI^?BZZ0+ZR~j\׀u`PkrS! NiDCsUMA!LstP^/TOF`kACZ`B3VQ 4 V6ݮ҄B*-|TP0Lk]~6qnA خ@g@en\Z{ \Bn_?҅&4ЁJX}I@ЀrPCPvu+SĤ,1 ?_\6??ىUߧ#s%<;hCz,RQ D\c4rԵW݊6 m(Ā9L^ɒ `*˱?CK.` ]3rHZ9`d s ˻dsjt^?h &;&]K @U ۄbx7mqh D̙с2'Ѐ:@&rjj|2l_{O׏C䫒hh:0 h5l|l_`s{/Bc p"/mVN(\Wj@O8pމѓ 8"l;Ƃ kOwG–W<8pU>(Oڗ|?z;I'W&yͨD]? $u65 uNԭ ,h@k0|M=OSYw+v>`-k *eoʺ{NݔkB] d ?>7mw5@h@k? 2lGu4}F|h#I>#rPX>ο$4λB 1/8?m}_Za;zg c?_dmC}Dϕ2/.o^vi X0_M8d8P8n Xy 2] 8ώv% co2] 6,>"19o">x6Ik[+nF|oPM76?+j{,,_q 5[I'PFMCVpb</O\CSG 2\G>mS#څfksq vpj\I&Ь}Cx8翭\8jTk P9Y9mkڃ 7!v V +eip~0P70\e̟|}w,u3 ^`Ycu8)Ԃi_@=@k`X^Rƀ83gx`$:Mct˞ \<")QI9ο CyܗBqBV:Lmjs_jՂ{ )#ƀ,pUoy2#B12fzG8X%ԋ?k.[j N Q&}y{B0_u/L%X}g0P_?Tx<>Ab\5\ _/> s cb~?Qk&z=cb[0͘#2^Z_⟁@9烩 by2|ve1ο{?Pe t^|68o:89WgĀ-  :\z&3731οU฀9 b, 9/ee ο&6w4Si`BԅExe6:LoIsͻ:~EN2踮P0G /?oBnkvC :{ϡ.wgr68{oomq68{oomq68{oomq68{oomq68{oomq68{d>~r*mkBTx}+(H,"H$"#X$,QԈZs>U{ ..T}6ڳ-F`p]k߅~b  О$wݓٱ|sCoA+q3lOx@(0a+? T,_7s\Ϙ^Bl1)C+k(FyN"8dPC_9>O0&l4Im+nwGrŰ)/tihf ѸX>E)<,6s45zb?J\<OM%O#(76:= ӋYAƒH Ls6MXBcX&ǘJte. 3.je(??Lj=%wZizFTx$kP8Em jAOހ>~؆B9 ֤8UKCvjbL Cy ;mj P. DkwUE€3ܨ8xUJs\ɟ+;}sFQ(KIXݛƨ 1 +KdX];Jģcx$D׷X`i @l̏rnm$^9΄zBGϞQ=nfkDe; <a>,⢞jk0B[p($Ǡp4 nq`XƓ vϵ.xHnorJ5Hu뇗 f a[Z:>36[g RL؍?( &w.7C#~B{] UW 71jk~ecGrD.=K@WDZM0倐0\xvqNZ ># BE )&yA}t?B Ym(WIpɱ |2+\2 )l8tl@Z.Be񅋍RSƃm>dIl'N adĢG3%#)?$s _5=YBR#-k"qGP-e"f%֩-ϓ378M9ϊ,_*n;HEBƱcl~ ˝[/sagIE2,z1t:kLș壋G){7ond{@rP>kwk׽ #kXfyEAB9uM4P=_lgW؇N#_nGpp ,ZUu6ȓVӰ0EK7*|]{75F\ԶzQz! uH>upT٣o3P)[^6` -d&*=%fY<^ط`_6|h3ء>2 Pq7ώ ,NsjF=B` 큳CiU)R鐏@LҮǧmb<2FHRqùFXi䎲OmGA}:*u f:@ʫRH.66jcGOpO- 6HKJU:Jǃv,3DZEƮqq7p?ȌK%ȧ$;?Qr6pP7`a^=R_)m>D3#£ _' Iɭu͋C-Rne㯄ssL<ȭ/R)|Lt_1Lk=rr 4/gEr~PnB[\g[{gYvRW' {Fem1{ wL;7&$xc0 n&u@5sCCձm8Heft x{q(aтa?Q%l4ςxmWI׆GC1kQ3iJh,KRO`ʲ4)%b6B8\pe;u)ko)#WSncRx{[sXv195_0Kՙ7>Tp5ٴl3S"؝LX睫[5m Q="u}pϘ*xbՉ#iM+@Z! Ϯ~jYݬ$?5mtu] %@݅:4h8ۃtu3; ΑO1A/r R*5i&j#Y2:$Z(ad@>'z L뇶6Z8|`6"X1_z' F-я?X^ A:?1;h/KVB' vOnFS ƤQ{=kh7MwXQp\v͓O/. N3HKRlK"q^Wh1wt h@3e6N|I;y?8t[[! $,ήLe"z%IކAkRl!3u8ځy?_W)AbCO!rza5Sn֗#<43y6"R߃CQ&>[# BHǽ{vekOTlq(UH͵h ݔ8,@tՂL{p/*L"d_y k,4 G̖bD>,.ok"D;|7[.DCA#ilϟI֬Dq]+eE _-- ڰc^Lq1~CCC9gNH8BkhJ#Z-`VoMa 9r$պZ-hkh ?C$ ^tď9d(8P݅]ڶw[wl;dn׆oKd Hބ(DInI M_(5)6H/Y1 QRk,nXHʉ?>df&6^EJmt{CCc`0ʅv5x<\9Yc}106"״!֏9dl:' 1H"z'7QqɌ#KR./CVgQȬ\ `?d1yuM6Ƶ8ZX]8^pwQE &1frRKi$GݜЕh3'{;;~FK37ku<pdʎ+C RMzƏ7)nҀ lEGyl:̑IoBS%|ЕsTulebA}Aʹ10A{KʘӺtjdLI=r PRg_LbR Şl?␔)![Fo wi&k^CV(t@pW2{hxHGRn͉eCbxԉ6GQd27\ثdS=\Ff*0ۣOP5(rZߙxQZ>~GAeN-jY7Ҿn;n?ӹ"Px}/NW:݊&׾:x" ꭥу;R펔 c䛅љElmG§a= h¨BG_uYnZ쫭FYs U"zM&:Gnu.DX5Xn;}ԫ%XO?~2&Frjj8 yA*W I9/ub)Zl: s 85J>~iI3Yԕ;:#hELם[ROd^GA˩f~Y!En0~/A Km>^WYq"<цF*c:xw|͞w%ehRgd9̕v3v Dgh>>?3hYDkgC(ʹƒԕSԜ| 2Q94(?OGQ34 fccPopTYaW(>@tX4`LGٞpɄaŰl\[9c26U M6f,'C4i?W~psϠ?kAKrŵk@I|>^xs?\`,D̒5W^w DMXf_8<%|8_왉pP1Wlm߃f?4:́_Ԕv M;k:p_sj؎qw]$F}y ,b'N=o0, ~M YR46+!}@~ujctCP.Y(x׎z?70WXFܣo3z0c8RGg0 TU򄽻w"/4֏CQ`[{Ocn]+{{ N!33+5]qpj' r9FDȬ)~: 9Gmx2-?sraG"yvUpa;Ră A\& ?#n 0eed~oq嶭!!DzP^H)>oȑ.ļԶ=Hy7S-M ?8ycߧq|#5"2Б lm#UeΤVbM͘jAc7Z ]> 4gb s 2WRsKg6 's8qzTT[R[w)I95xWj #!nN+zPڔ KgTE,?{^RDݥ=Ru^zîc&D'i74SJߔ&HUG[crͦ<׿~4}څh;lpAZ%XZ;tQ?yk1+Ƴu6[ Dc4Ɯ*dB#!}e>samhG3c^8u9󼵕⸈߂UyB;f "Yi=D =4&|C3g]~WgjhSIXU"1A5Fr4{AljwTt6</N \Rta| i>T.Wo>>xϯY{緷m,J{gg}v~)]s!?wXGFl!7U|Cnfﳅ:.@mq%臔Ru?.:aBֺE#Gg'yXDuSWNJD)21ѵVagWPqȒ s?¶@g")s\T{f3go^w:^"{d#!φt},nyWFKv„X4|VB~,˘_&fjp/WԍwaO H 3I`u1ͤ+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_Wݚw) mkBT(x흉o\G˥ P!;>b{>֎SrDCi׻z>wNRhK(!@P K(wQ(UiҨ%%j߼ݷ;J?`~7$7x~C\A͐dl9#Txx='DF,dzT]1bL/%]dib,ozJx}d`cI{LΝ,6`vs&dp.b )_Qç$b1*I?D^{$bU6ٿ2EW"࿜r.՟ wdŬlQ}Ůlm?{; `m_-$R_>$ϏJi!PI ͔g}'e-Tψ~`p {:Br?G;쳢NTboZ)Rv_w?<._I?LGđ3ω~}z }FTvrjLGO|LQΈs^=8*,/%lo_0}w]=>Cρ|/sZS4^˔w?vOboEUߔ)v?>"̵;Hr2aKՎw4[$%uo}ݑZxYO{ yZSpɡS^F 38m=մn!&jd(]<"~?|R #d,-]K9L> |[,1'˝%\L/ٍ^KirG1YQnK)/n냙^Nu>xҨ퓕t;x3}8g)mkNRґNA;=~4_lE lv>=#pwUuW7/+}fU&e5_w/ĸ?rWVB_}OrjSo$f#}^ Zmw/4}H}/{|[O}[Wx  ;,!xod *%w`Oz&}kyOau2x77}~VЮ8R>s$?.{X܏oetcy~3}Tkz"orS]Sw2/!S.3hkcoKP_M[wƦhݵٔ+eCC?8zPP5z)s܊2]_}RΌrx?,|=15lG{}P7vZ\jap}_o5[ }f{]zОe.-JG>jx_øUeUlw.ׯXX'EC!m{'OaMΞ%+%ј?>oO7wq/ ޑψ2o:}[ݤhc{=-6kwI̯8~|p=Gr_&>O7~_axS Q_oUU9OGx|糘j]UyS_wPUeu=.OJ6~/EjSwJ?'XKlu 9~s^{q*kjq۰(cpN~+cO Ǔ=8rPTP_SQ[ewS'%[Z|} hNR6r⾸|?7%wѷ8_-ҽo?y\9QƂ:/~+`knwG5'w9pJYQ=Hl^I~-k7+*׆sf9ط_ž|[30^ z!?o?i~Ŝ 4#(crNaܟ/J^Þ/55ќ:^_Scp>*Dһo7ƽֶ~ho3>zO$tH?ccUpcy?c߯*SOu_55yS߯9{k,#cpZa///爵߯|~&~0B{bdyķ{>( /ySzbߧzw@J f}2};,3/=(~kahH,}ߏ ]0yVP-"ӏݩb?ݢ_qߏr76ޏu_YOb_}=ҊAgsP_UE gX7 1 h[nu } _ }oSM5o<>c#5S|d{~"n?ЧczZ}>a;deԄD}K;$08%|G>+ʀ?k|%XϊJf9j憻 bmkBTUxs[We((0Ż,/dy%;qN&nS(K"ɲ%vn'NH&4M$3eֲ -[RRBI:='=;?}g_q{?8A~J0"+MdU]5YXKXHisYDַ[Ɇ;)s6RTv;HRIHN;y)2s2o S^<(n7W/֘M4ųۋ_g*??uaEUPoΕ2 ' gtzRe70F&Nn/J$-K%c^Z6k/ۋRU˒#dd3ۋRToJS9IW< ٓۋRMN_EnoP_ÿ5[oQCdh1Ee߼]oӮ_ e/!tsR_ߖ OQPa28uDw{QjutZdk[}jݔfΟ4JuGA28(@oI!E7Q?z_^ZQ1퍔o߮J6Sߕ{wHhlw|TjXIi%̜eMn7IpdNw{Qq:r2UeJM$ȴT`&vR,a5F^%1 H=P4Ym= z>{@OfʹP"=m[1;;5kKbk@^Z=ݑ}8uOSW c5T9s@&a 9 c@Eo쟠&&n+*Qjze9oQ߉ޫDl1`@JF?.z"Ȍe۴K" 怄ӝ:A `?9)s8""{fU K Phl0`Z `q1`N1OQOE@PTU궶1@GV1W|Tte`;D!&k̂F@<'YmE%jx8`L% . E%jA.\s.Y`Bc@1UbQP>ׁ_cyQvsKcDvyj5P,P_0B"GuĢ(yTmI{o,ς`Pl@ { ? ccȲqKPݔcc#ۊJԂL}~@7" !f u߀4q8'`B #I%M$qV=ȑ@|-80R3yU p,XlMLǿl.Fu^=H|`b,-|?ei?p? g{EāӤBz&N-v:k\n#ȱ}'RbA@:p|? ~x# `g|$Ȅ% FIL7< L.l;68n 8{)?Bw"6'2C].Tf?I*vRpD)Ȕ\̆dƁFִ̋{c q`HSfuZ+6 xx]8c76w@6=(}Swr] pN+=̌4>5so5 {]R,hʖ>~}h;b`,M.b7E?Ph?bo1o! bua_? |}!7@ 5EU)RM^Ľw+rM !"P!7كuAT( >aw(-YT(pv;pdK#p^X(?U bNX$?% J(-1aThp|w5ߓ 1awط}Arup=GsEʬtJo]^cy,p ǎ9@d .*9A>" Kw#w@.i|OHÁOJ=B;Ċ?8#4@'{ w7)&}6tvHA^ 8+lffa^Hü@R^3}bE<'<ޠrFVA.& @ a0+Ќ锧L mkBT xMlcDZǎ3q8N8|+3pLH230X D+*UbAvGl`W*.PUl`3ZH @s޻N:{_:}DsCH0 ,bi-O[̟?m1bi-O[̟?m1bi-O[̟?m1bi-O[̟?m1bi-O[̟?m1bi-O[̟?m1bi-O[̟?m1bi-O[̟?m1bi-O[̟?m1b/ XF@f<]]-diPr;H. .R#Ӆ? W!'#0v\.љ1:[2fOc{tyŸA8 'b%cgbYȁ͗'bD?G\IrsFVļs>ǐ+/'q2qzu 3 c  ųN\^߇gg }}B8U{rϻ^|؆Z0;KX<޹+ZoC} Yz{(?QO-KKԲ:xQ-~ aB7x}}1[/zGze(\6j<ñC.1kɉm.PvK+fdט f\jL.Tj4 vE"as=oJE+=%( =M7pǪI}s.efȁo8qb}p-ᘮ7Nk|B8qbZ':9wG/'{`QXUj=ȗϙG+ j[Ծ|8>n&FM 5@ 0.+>՚1qa_[9=`{q߶ڗ^}%{W>y$.VOj{{+=ԁKYq߶ڟtվ|kfGV >?k6o"̿-hP/cfxs@߅c3}B̿-:}uZ߃g/NOS}{-= Bcm%߱ uWWjߵX '߫.Wzog־^i? rU빟%jU2}?پv}lrj9k@.Ն9M0SXs%{7|Vr/s?q tE}_o4z^b'H(~+55mVY y!z116r?%y.xu}_4\wCdyо/_3=~Mwm)OBn=ʮ1|^ߜ;ǐWƘk;ڃ{c3kMx=}b7]޸s1: =bP؄{y^e?sc7~`?zߪ{>T1c2Wf~Ofٳ\O_לj?I,zLzhu~Ϟ= bu?"՛LhoO𙞛v'x/_1ٻ[׫CYz/5%Zg{~o׎;c}bG`wy{1?n2GM3#ѳ#ߺz~S>S_gszH1| 3_~1_^~0vf*?s<9]z+G~ʺg~H,Եo>pZ? S?Zg^|7Wg((|0okzr~/`7Gim94$#lp_خE_ Wr?eQ=qc'_EUǎWVr lXI o&_ن.NJ)?VEE}cų#oYdf9 ~<ˢ|TnjeBGtZCS/T A$mN3/DUcDKr"QBb_q/Xeۉ c_ъǰm/H1.M# ?xrȷ.p{?l:vR&{mrloYd&LwhƯ__=~!¿,oH7W_/vE$]V|g(/#WÿjÊg¿އg?__oYߊ}eW'_\#}죽'E8]nM?$ÿ8Iw{?A>w2M_jUߒ2E eo?g rp;w? W¿猷H#i|}9َ vߊo9pƇf5=DecNO¿ m iWýkL\*UW?e_ rH益{޾owˁ}V<{ZK_׾79Af|k_*)w?L{=~^i+^ ~+zF@_p?zP=qyϵ/k-q%zO{iϵ_-8ϛyn_?xx{z.ңǎ4kw¿Tk$}>|㍁z?8/?>pe>=&?rOytTwym^u}X3Rk~/҅i^vPv5>~HWg}{=xNn'gpP=q.N?/-7kôy5\=_||V3Z~ڰbC!}U4Sh_r?V{Y29n^7zn{9R~{:Yש{茞cwX3Գ?g(rHsmkBTWxYlcg8;q[l'v=N&ـ@+RUC+QUZU}QZD 'Li: H0Ks]~y̜{q|?yϞ={uo|NGRbhi^Y(Fc+k';X?TL\h1y5ɏGR'3_߃GkҿπgYYloycɉ&YO3\1R_?qj1~ 6!#%/> gf@i&cETO2I?.㟐ؗ¿^,5*Y]`CL!C%yZ7 ĸ_.LSs}yD8f-TeI3?;5*t)O%W97?1 QsYxD%%+?]濢?5w=A@^C_Ǵ~: d[oTI?j|em6vo"F́sFF ])KFwOD9P_\c33K?3@e_q{?ޏ!kc5CjT uoi-0 5O| n0Q5X _ znjT&?~UmqPswdUoޟRChk@e'@e{oxr3Kȿd_ot9Pg7p ;{WJ*[V_?QB7)` V* pvjTvɿ? =0Éʶغ4k?^kUG,>LPs O<ںS!qMdo3p͸cziOoGW/@eB-_y}bde=${pVjT׌?wOh~oW::کYP=G~o濜vQz=<13*;#Aj6eu& mtowZ>NMo| >jo;o ĺ? M6q2aq3=sx@.{H>}:BXПݱpkyß_φ#x=`Q:5{K=@|32J͂F:m|˽sBW({RPKIлsSsx{>Eĺ \ˬz&р=so[W4! 8o{j@ Lл6ȫ uՀR #1hG5Q|ɟ;>g} 5*{^Ҁ@;FBW P/ tFFρ̧9y8K͂njd́ǟw+fAa^뼴 \y"O(< =>XM6- 3hfH,(잁_N(ރ4R`j@.Pb Cb΂]]@]AN }3&M2A̟7s>`kQ Ԁo:uR<>70ϭ PN|'0(7΂5@J߅~;f}Sim;g14&;!>seﳉy/I8B 7usG`XM 1A͂V]o4rnvE ػw Pny0?iNp), 9>0'"iR0N\ Z7g'fAa>? bydZ**O: k{A@i'ކPFxchh^0sY89P/h)`<0lRrr&;S־Pg†Ձ, ^l@ޛyYPٳ@^Iwy~{P\k5 sttvnķB@9 H~fAeF.4Q `m*X];bݰZg¨p}+{p\5ᦈry= .o;-JIIr351|^jT1R ftׂNEy ފ0A Уr YP`炻Hgց~`tcǀnyP] `:ƀ- a75 *Prؼ1mvmRu'XZOZPM6}ƀ;y_!+A#iAe`Ԃ]7D8:{u\y@c 1`bMF[zP ɴl. Q 2u\c:!B>fAe#RPƀ~c]PspR=8Y *D5ԃ>дl! /`PFyI(ސԃZy-c9PYw7V`24=BxeE :Q - >pbg.p?Z΁Q_ߐckA<`hxsl{kpx&3V1偹l@5/7TbߠbOU)p{z@Z@-4vť<~ȼo}}(2Q֑1@ KynxY=G3<7#4A ApR2': /|jRxSvЅl /1B-p|@c j zG}i|eOtZ@d\&wtΛe/P?h2#2cp%_M 4~.Ai`G͂ p|`d>p/҃5Knڨ}o[5 [q"Ł+tjX8082a0W͝RnFs@C5 Pl<4tx,@Mr{DZ|`7ܙٟN 5 g8&s+ 5.9 g?5H̓’83=OX.\j9-ԇ"O%r1W\2kT|tX=&pv\_ʛh T,P6\8{f uo*>=9luNmkBTvxYlTUw1@qIT<`b|%stfi봥JAQDDDE}0Q#Qq!FEj@d+a \S9}_99;3`" l:(~ aLEeX9PF-{h‰9vIc2 kEc'R9U773\WQlBMsQ;Rc>tNqZyz+YPDz%NzSh {aoaBN~c~eEO\Kj+rqp.Fމ&Ji8emNCdzs*J=wv'({iZL?$oJna5Og;t~w01|7:UyKgW;61_/v-~n8?.JxyJhm[5\ &U9gx~|BCZ?XGg,:'e?n)w/wztgxU-?\O￟{ss̼G3(ޗ1eXexo."ߺ.;?i˜hI׶,|c*yW`zvHz;N,s=&rw6;{e{%ywmEcʩW矋JqjunpOKpF]%L;0H&J?r/z9L{0Ńw~ueuS+Haڃ }-k5"}s]~l6:g)SvR([r.s!9_*}gp0{cv5W1I'u̼e{?i?^3S=W 9sh>}Ir=F=y<*>+C8/{|琉-r{\3^9㺇vEӿ_ô|r9(k~t}gͯaYD8x5{M=V߉5sRߣRkoQ51*C\ǵz_9]d7 rv:Tx_&sz 8 "4vp F2p#auƇ0_ 4wyp T<׮ô/nP4VtoVLa|f9O­ Fܞ9j8Ck6ӿU1iקcv~bej|>~^ôSɭ9l%Qc6v\}՛a}y6뽾V]ル[Laڹfy ;7>5s#;ݖeF27 d542#iTXtXML:com.adobe.xmp Adobe Fireworks CS5 11.0.0.484 Macintosh 2011-11-29T19:50:26Z 2011-11-29T20:24:34Z image/png >) IDATxy$]FdګRK! Hl6M`=s6cclfx81tsl09l!$jګޞ{ĽGD\^WWߧNVfDFf2eU9AA8\I  PJMAO(u(`@)ݧyu&xZodGGKh64zziKZV"aǁy_N/wAzHb^%zHn1^ct_L" B;ݶ@@X'tzVI w0 1'2")H= {=^RǭQؕ{Rn`{cB3fqv:uXE8b\/ N2pB_j-.XD& p:'H,I$H]{JM ʡA;J)R(@'Pj;¡^)z_m@}PK@*KYfP@ĉ(rY`{1 e z:px "=14Z'J(Ҩ&Jwgs8g7sg~,,8u΢: &gSq iVlGm.0 %4H_$j; F 0 ˁWD$@QJҞRdR~}F+2&q`A xhA6apYa_4fq$N? :@i/J)"IӡX^#jձQ;`ZhdH|ZOm_D&aaj]$|3$~=/mllJ6(_cqLդS_^F]K:FR@# Iy/#%])O)S =0~/[!ȗf -VN}9qFueCҙ,Zvv "=F VzR)2YELP"01bn6iױQ'm[[lvq]'K$͆~ KV(L IZm=*.k9yZiISdyy"#Z&"Q{ wz F.48$C/~xVu&jp Lt)Gd~M)ey>^P [!/yAE4k֮bۍ#ڸ7DD"IR+ Dwtl$+H J"ڔ)1Y ~&3Qvi4ԗQ8!&-i/?j/Mh@#@+72h)iPmBya8ҡKt+Xgv{H> {S"&0Y2Ջn kuVm/ϐ ~V5'lpp 2^UɖPJ~AqUWQgmo*:I}@4YkCJ D?*< ũ?aKAt;]V/Э/&=bt}9Q#K_QƟ+;lI ;aKAM:K`-6Vjߝ!?H:3֞0}B' k%+pQbK-Y]SNTapp Ig9etNlY0EWi^@< ZV[D& CrແW.)WRi3'd_:6ip8Zrqw l ZÓS38Q2y(?K~RyV pαDcyTLxup.^Qw1yym|(&m dp|yl]Q\~VޤM< aT@Q)CP A}}/G-pnsX~ZM]& "I{AE鼗-Q]8M& {v; Pa;`W'paXs'"L-#K !YZ Ϊ -΁듶 `(S[Zq)Q;Oa+8bK׮]uc.'?eo@L{Z)/8Q? Xa8Xveƭs8}Zmψä 2T+_OH[_A9X]DcE݋.'^;H?\{~&CaJ8A[dueYl\Q|GZI۷pKN+xJ{ǵS=CTy e֯` iàj}ߗsAo&IӞ+{, !*0^T,p0|lW$pavImry(/G6y f牻mpѢګ$|d6洟VT'I'p`i6^"qw5:pǵZ&m~@-:k?7eT'_.j6Xq em2V}tynjx<# -<#g0<8ϖc I۷_ I~?+R?L6i* joDA8AI#a29pT- I۷p0|+sSN)1u>28AI G^pJU -'="! 7T~b2  2A@u>tEJ0|`YDlA{3&gj~2R'rY*G9pjHBP01axqO %Q7 w |II۷0BGIz?=?@e?2LArygО;=aNOھ0#23~ܽdsI' \@y>P?~2 éI۷WagўQ:8Oq^An|HqJ{(E{x;O0t."~j=FgO˪~ byi1SJ 04oz:Q^p\)OfNR,W'm p+A8hxI6i |@{ #+KA (| .0]r@?Tt&XPFAiQdiARd (nVa _(~n)YIအbj4^|2IC(P 0 8SEx"Shs(O GMuLBU ˀq:oaCJ{ &Av?2gQ;v |GrJs?2&)ϟ l6GqI$P|hO)|]:I._y ]P*CkmoiҶ- nЁPJG)U5 a<=ORh玁KOڶz?,h?U (R>R/ph'SAaḿa.8=?gTfOA8(̞d y>J)<˗(+rym#HV_ظmyZIvl,0, *kg&mNCaZ{6TrI&;p9_xkWDJ Poㆾ==7I|X9 tX7tzc br|#Y82A)A,]=Kk FK6j/V5ni_ߠW^樗)3}~'iVxs<+4ZuJ ? vVя7u-m-:`p۽CK{>\))VtЉ4,|btu'gjZ['#_xUEG$h?Uբ=~+/hS9!_s-s/ps^GiM>WDiR bVK@+cR>o8AsCQǿnH^*v_h/8WO2}d7aG4uΞ} ^%PIu^)567x͑;7w|ok7-pna'?t@ ~P>VaGsOsM S-p)?CT!8J t[+Dr^у$I۶M trܤMZ\~᯹pE6sE,\݈ԷՈ;m1xs<د[IزߍtT0o,Z!a01Jؔ-pJ)i1vnQ(Nر{Tf0~;AڕpQ9Im[ӆ?x_US=:a˄gy܅ dyZ&덕Mξ{pD 翥:`ԁk X?cyo7H1`#s0Ng zDg< 2!#Z:nl&GPevGN13{?#+ts,_=Gk2.lԾ|{Z9iƱoҪF)x%(O-L4s%Oq(Kd* :ucDR#f@v] #u CcB&Mﻡ} ۃ 3|M5xqbgYm, g|IaWs/~fN8E"&p}οtch{pߘc&}o#hݸAϐ8p"pf 6v-(<>߸ߨX"iE;j'l&@y/מ?syON4a㘫W󹧟╳ 嫛7Fc( կH~*QnVwn wh{Tl׫'Hە4lz _v l{Fݫo?z!w*Jg4-)4v>4ݸѳ~q*(,'=rE}O0$mS&_wj7Y30lfӹ{|cHAmCzmX86 s8۾PZ 15lM+םI6=+0<@N`OœJ[6Y_[橏/G^f\&{6W[mnP^wAFAﺯ罕(? z 艁D $8&[Y@>ų(:?'jI7ʞajSZUtV(6M\q}B ȲHphz+0u#&xvpW`7xzwcj ׳qTl8QE(%F(QITX5VrZUk 'kڞR{Rc2d_ָ|,?cy:4X\7E7pvNq1hܸ%Ԙ [woRU?ݛrz`0a$فT"+C 5S,u97W~ ['m {NaXgB 9YK9qq%:|vŵɏ`_:!صicԩsnpȱNoǏAKtzzI^F dVF?Tz7)mD[@ksX,huF  %(\g]j)sZ(Ve.a\p>:|)Yep[9q[8 ?nFp}5soG+sߦ;0jl^WC=A'9OIJ}M`=$?P0n|`Q!d߳s`!YM޿Ba%p(U4W} e졂=%0)(푯q>Y]_J)c+鋁qN|Ӟtf9wN4nA.ȑ6wkw۴:#˜ލ`T ޹K78T Fk-"C=;: T2 E{j&xOzR8l |OjK 8WUF\ 7`i ?K՛r}c` )tۤGٜo7'e</(F:Qn1>Z)48ڤZbk(~XkU¹g]եKé~=oG@ؑ;cOnlvc߻>Fn"PTST3Sdsy<Ͽɳn7Y[]fZ} x4 er~4MZFvb(p*CsݸVIЩMcscs7!  @k\*z4uLaCMzի_@Yk`Z>pvNazkϟS:3-S;up8yO:Nc+5"nTo ֑4&?9} Zk:Qԥ\2[]`n |Z p%-_\CE6:v/Vͭ`_Ml߉-$[`>gm&׾yϻm_zN}g;K6^~ Iuk :_^@zD.яqZ_e6VNg(7:@ҍqz+h7QqtNSLꩽcdyGרqY.]=GJ1W"h0i:̀M^2aFG&3pa܇!N JYlMD x +')OOaoj6g?s/||ȵKc#A\Z"rc"d srg<qr 'ٗK.-]>+/ssEpۊ3oژj8a35:Ur7>+~6`#wg-7wz򵋴V.Zu~V-{)AtAy ՙI$stY䟢i,Շ JV)捫IoN\sdl)I;8}e|ٛ^3c昚k<ܧXY_ZKk7>0ArUo 5׫eF>aF7mR+ tJcbp)Vԗ+)o>8)&p$H{imla^J> gXr5u1(w6ƃ;BDhy5^pgKPoY]_&]}39?mj242v$ 0T`7{nՕԯ;-nVM;J+2e(6I \x>N&p,^p#NS5ǿ)?&ڿIy=t8G)_Mċs_FΩ7~33ū| O UVDqw#wƵـL,tVBz7 `#R.Tv;w(S;SWmWC;ߙ=a8 *6:Ppi[|)iԯiuFm}7E" kȰ5.RJCCtk<'|B+W\@?{dX\Jl#i^FiɴiF/6gF }[u=~ex m.ϡ0{o R/}x28c\g~jVy^EAi$(6 0 I|WE/S"Y|i6|}?޷ӨGoQwߖ)QCtiVti|́/Sh9vf ṳe<5rJ/i,&`zuw"0BU'OlLJ)͸8j_ݴ㮇aΑFJe|cw at?zrNf67e^p [Fj21mtcSԟd)׾׾q^n?K0Eiսݘw;jѬ LDQO8h(EН6(W xwIܿWƛx~J&?Jo\L`?ֺl4X(8R8ءd h3=2tKs(+y*.nRy/}?-46<#MyEßA0ZWŲ"12,p xݴ T|-Qʯ< UwOOS7C푡!Ԉ1D'Gm03Өyӯӧk-k-3:}?Rp"`"Fu[B)EFixVB`Qp~79_M?iS9S9o)1p/k DpyY 3m3vy!4&[mJkhI = û,nfG3ll._^k+|Ovb2ޯ)~\?l(F 3m(f07d' ]2_ @{+s9ÿG!_|ɏ#?voUܷ[uit9]ڈ׾M#߫yގs|Pܴ< ES{V,1ds9^-.Rڔ2*GUx~˗hg-"єaC?L}%''9x#vgC[O@%3"+@،R\)aj|72+J{d_dξ }O(_[nr[7U0 Ec?Cο_7 }%m7aw8q>~m8?_6fz ó[v8;"B AYtxxn 0 [~fگC a?mU(qD^QJ.=aoG&M\ggaTΝ @8\0. ͥ|` $(LybLGjhש+Z@,D+h$ Dž C~뿥7`NZ?Y7gw4q À?cwi/,p}LK&L"}88W[r_8R˼%PQk(h/N>=w|C3׿ge~%K؏oRo J|Kw s'k,ǥq 8pMgFJ(i=zkRq+<);|{&` L0?g7?iv.TGW_ ^VJq.'Z|e_**nULx^nՖN;?x*NW/?w?ƿH6?)[j>/=Ç\hy?Ek& 8SUҙ~@J+hA-OGGxxI)ʭ+N.mRng_߳tPۧ ߿/ḍd?+UjF\<< "W0Q'uği Zr |uuQQ:,ZS/h?cDd?ҩϔjba\YGu<^}A(7?:N#L:r}dB8K8ڡZ_@v}$Qڣޖ'hmؓeu*2E[wd?_r@R{7 @88VZs hݚR Gv7x@Rͅ1y|w)n,4xs,.Z37J\EML vOh_p&6Mi\]کkJ*yh=蟙mOH73?gxKhZL?\N:h%RpuSAk|Cs`Q)VZю捏?|wy0-[޿ɎE?4?*4K/{*/@;`Vg1ZC q\Z+9/KrBU)Q Rk.楄S?zAyCcٻZiG88㗞hvL&QJ~V.EeP.h4<6p(_ӎ[njU&k&[Y RrOz'2?ݙn<e8vyprZyhשiﯮXtXGRjjmsfc.ޠLz4vsrtW @yT1QG[K(#7pUr:vV,FCi ,RkRNTƁ:QaM9NMkjyN >?fdUSN:_TjZ\"z)i#O>~'5#`L>"`2;; TظwEo-0[*-A#R *+ U憏>zJzZ럈H}CvDEs:& rYp&Y\Zz `L NuiGk.<`rRMÝu~4NS(9`a_ }>>:MgD(%իMɮQx$x*jkM2QgpNoKV9F)pkѬt)Џ: p*q|pZ|;T߿ST@A Okݿt;\ɮKx$xs|?'mr_[ki7y'nA8s,j:e/]⥗B:qQ]x{ҺmƩ4oN7Tj􇽼ge?v3;~kw i :s\<'6q\έ\^RЎ \D]GNwPWUJy_|}3 ScN.ANDx؇`q_G~&F{f?g8_, ÀA-Z+PFT^~ Tr*J9TRZUv6uߍTSV3_GjRh]jVWWw{:ZKD Jx7X\Xu;,I~3ҹ>AVю @WD'*PSRV[SB\ = vOA͇}ȍǟ? D{f?g)dvErOYvoV[>Aī6P Ʃ-ݎ] 8JU*^+5[;oލ]?d5?=NS7&+ϞAJCZE9J*ՙF{3R8ڑyn/ ~"ٴF[?LyhUAiq*FO7=>Jz|uS^r_uBr{f} "AN@A:Pf6F=]܊̷oRtz۳IX^@r Vk>ŵ ׫vQJ@xnֱ3U(P@ޠZkc'H."{' =._vA(9^+D'f[*xZXJ|wk<'VwAQ?;7s9.޾ɭ?#|y3Zm7,DhG*JuQZ71MNptE)GJǟjAg?@ψr|$\)O?z;@jj..IwLhC :RJq#aPJLr7('tZT7@sq`Notw67Zv4P BRKZ?Yt\ *=ƫHll*Nїe;wZ60Sת B!1ư c9;@Ku\Autbg\뜁v "vki8c  {luֹp ; `a-( Sx^50 Lq"-"\<>{Z O2v Nsz/JWRSѰB};R"i:ؙVJhSi?1$Z9t:v EiץǫTG{\Y]] rJBxJ˷wL?9;_ZVp4lmJSS:g*Q+k5o]ZOZa0qw"<8EPnXkDvfY'&(q1/W8,6a?Uv?Xѐ篞B3 '?u6 w֌1鉈oYϷiK`OP׭h (!x hFPZ#~o?S:.ať]pwB獿v(@rTZ5 V=tA8(\xJ9[?] \J ߨ1a] 셵65:cT@ȉa HHZ&N naⵍc*#q8^ 8u`]W<J(U(+*FTSe<ǣpta+MF;)aͤ]Q誊U}8[EƵ1Eʾ68(\WLdk>Z;?r/8 cJCqЄ\tTV$M0 r39kF~<ozRzvr8*+>D k*fJD-q󫫫>EE+bP6n~z+s"]{(epiF8X8S7k_e:SF 9+1BM 3ZE)]'-O)xUB+gfN<5totL>XNPJC?jx xf>MőM|ϊs}V+ҡUJ! KW٠Us\OJf5t&YLhU4=_o)=KL}'>'[chԛ~G;҄J9PRv70ֲʨ)IK;ngV*U*Z>1Qb;Pu0&"2)0j~8h4`qA`(Vg"PJ{Zk+*2h7Y߾7a3igZ<0tPY^P$A84I^\6G,A㗮clLrݘNBX Ƙ֦%翵k ďay2=f:X=iE *Fk\qJ{q"~q?=Y>\v #.P8Z0|2ߟlX䙧^9e ¾q=/X "Zʑg)(7T0 Y\ЧpaHi4i# E808@*t4O,no2=3DjndM4RIBa4(O~oh( hLhKIyA0ީ?%v纒lpܢ!ю Mu8*tBٸ-*^5rWdM,u'_ШR/=Ƅ{}Ga~p/*MԀ @4H+/!@Wi ЌϦk*fǢe`'~7vzR($@Ÿ J;F)p,,xC('[;Do'(PP8JӬ}I£Ga*KrDA9-_"#-C;UP^:-8:3foKaҒ)1˶VH Qapt *<6OY@98ZJh4 4]T6 \9% !ϟ u'TB|V OB]ec4Rjn?ө, ؅GIKOӛө8h^䱱M&@h)w)!wglwN{l`LDZХ\~ <$AmziNAMZxD60H$TXvZx^GMgtXXFfQ( /wE0OhG RU{P:w6ly{`jX쾄h̉Pn(\"9;mRE<@(#ȶ&PSWbsOv_1 P*j WdѴL2 *ԉlVdN@ X C~3}fOQ vZhC TʉJ(#?`'fL;ECq |Zїr8 IDATgB+J%;ME+OY%@IYIwLSpSeyUx~Y|W(V!ц?"WA٤^mD7v+,>E#@#&Jdg$ߟl A\0G(ד+ m9V1#H8m67S$v)dZQRHU8>LчN S+NVPhRJ$PF'2ϡϝ'R|.S \~Gt1rQ?xJ{LNezҾT@ b b1RkƬPP\nw;c `1`Dիz KK*`5V3(:s UI`,hƄh jVsiep2(k L;),#8w)|1+ahX_7d}~OBkVC츝uhR9t'+WoPp (@7:`"˜Lѕl Mnc/=@8.LhBEY6X@AlvPq+j Gk ZkghGXcS' oZQ.JQ8ݏ~ﹴr{H;vȈ6*@6h@K ttgV eЎ&3:{J h,.ۀ8me]E( RK/GW ˭ϦRc*(]F;8E]tMp+oW^w3XU(?Y+Ed9zOu,u&0 M@bw=r9O[DZ3ut}v5=V '' I&zƍm;qNʇ+;E ݏB$!{sJPB6 DOL,F(ak4m/Z祗Dɋǂw̭cH :ݭh w\p m ##l=Vjܛz 㜿U3bt(.x n?#y£S)\4 )c)~<m81kIv8hn,֚Td.u]kuE/ \_H7`f12X8g~*{ ̄S`fc]ũ^pFvYΟ›o΃- ~>5ϩRp`,Z;R  ,cB`(:Z __o`Q6J',t_2O= J @2 @?C|m0ϐ.vǸ|:KKqI s5R^j'v P@RB}{qgU}+2VaIK8 -ra/pU vx7Zz-p*.^on1޸p+@,BT4[ {)< F_Q*Z73^ RŅsQ3axK/Y(@Mhj VXej\0U8J h5Kx? Ci,]PU8 0s/T+566R9X(@4H t8NA ଐ텞Ie/3NıN/^@6 z[;'Va>D$# S$T:geH# n,bC3=´lv9% s56%u `:BA`&D/cV;?pYl# *Qh! @ZfheoEXJ DzDMyt[JV(&[TT=;]ZeMd`D[6<]9r,ϥpZ `m kȾېwzPɲQv}|.M^hZ4Zwb4Ȭ~b MD"$.J-žlpS Gki/D Ns}mhZƸ 0Jh@`-UbP("A `g\/TGnOzApbxotm(cr) @F)sS"R1#6wOn9p ـc/v:5 &D@! BOV{͝iXLh⸴▟¸IH*d: 0QY䡂pfkvI"Zi5hƏ#9ېlI5˹k|TU\va) vHɲ,d nPq+>L7}\Ǎm+<ʕ+O>f@&o-jsBƄ>D'!ÀFDB@k#:Q5qo+*mLt?O@ w~"ް;oDZ orW%3aFicc oLSR h,0Rn-2v< \d4z3ŃoѮKgCgagh6ۣmt/?8Ѷn}F4k.vz;\}ƉQ/B1X!JtB.c>f@Pk{9N ֨>aևxoRT`"4[ 'Fa0auO>Q0l#k /ӥƕOE9 P&MP: w9xqJxHvj Ўr4 û9;t;56t,B(a6aD!#ylK#c02^t,,. a&!٩a<7Rwّf@áPM.:(>6ﰳ >$oW,-0WA:ȢCkڄ6;ƍT&;@k|0VJǥ^mw tJm? dpk#ïJ ., ˼Ǻ/ T5"v8\=F}kkT2*9Icń.#jxdh6 .ms5jNo?1.':ZgoDzy㝟sn>A~mO|qqz~X$L`1(!ItF06eciaKjz?5~,y za8)q4 r zo{Gyz㭿'4!ӅLZ"Gj1}מ1Z5!#QXÐ(0PM:e&Ę96&g+ĚL>J8V@DX\ݯOcYZPi 1pFꏆ7˽1wUK޹oPyd50Q nN$1+Wd0JsRL%D)<:Ihq\ L]vy]@5_>}lXJ^ ԩvy?  Z͵q?9$̀lH<0h/,t6[l݌::% pnZPt-8#Kx*=ZK|;|~^zu̾GorwKa}Ѕyk7,.?ao`XK2E`50_p &݌N28K<0?ɵ*9*QCї*!3ӛ3jЎu+owKs,WhQJ1t{;l<`sg %fN؍=NE$#JϾ9g1(k| sy"[o5Q/KtۑOv BE#D&~Suw J8y./AMӬ/vo{EIqjhEsaA8vsJ'R5:ۼ_p F VYC}hm5Cf@ecOI*' H!`*OC7izɝ{O~™ZB[Eɤ84 S8Eۀnإczw 2rC|/vf 2?bkGwF>ß\#gc|0>^| @ q {gX8H{"OX^ӥbZn+ |Y6 ǻz U@Y'Ϸp˕'$%%>m@TUwZӷ~ub("j` ~ywSa?ˏwl>UY7 Rm,RuarlARSU:;,-/K" -6A5} RSZwuu>boM/%d0Bl*B^)XV%+V(' QqÀwnي6m[ IN߂ʸtp4p^YZpx*ٞn,r A0"`=cTX[[KƄCXF^}^?jYi?s%>boKI;mwAJp4 V+n ~V4o4@)iiZ,Αm^bm|a%OuL+l:ߐ@ UX Qq9_kڀ :#x7չ,gSW*%0@@u A(DŽ!64#Cdϧac0qS8~e?\qgFzIPH@^YoןAʂQր#@q/oY A0e|p,.8.b\8 `|RQTM!Hp͓] BM1~5 l018aM9(/BrujZjiD[9`Y"#bs&jA]:Z[[+-! CF#gt90sHL@" E%iA]|cOدDX(+K.X mF {?8Ҽt:XX'7= pbqfǘzGNZsUiG XDxdC̆?L4_}cDAxX `D3ޟu^k-#\plmTK-8 EÓY B0M:uZ 2@YiU,/^JDٴ;,r"Tv[iZZWNS' 6̰Ѱ6*3FY@= 0'@phLP jZp( |q@ESJ)ad x2-S;M_ d|{|;llݏ? vCk WyW|Iw?eA J@$ bo`9u 3Ͼ\Mee5 rY#ABkoL{ ‰#X, ~6R]ob.B;v%sڞ3anZ kss]w Pt`+p4֝ΗpbWPln?]Ee~*>7c ~o/ak>9q_XPAgJHHvu(OQ_`杷}oC#F`|mmmsDQ&n$倥+T*K 3aqOAK xAOgU(*[ dx_r0*댱;Nh L@o᳛L F}l1C= Xk_tÄ8`D hl 0aF5@Pb}램' #>. ՚|{v9v6Z3$ @Z۳Q#J͍'_ ZpU X(3PkM>L: 'nl_\w4Z+FQGJUZQ/Sw_0#LV牫Pfs e1E]$<"X>'TAx7h5ʊicI(h5NshDŽ`f|DQ>ƌ dyNRhlg$-@aIiG&% @R/f#ekiJ*g'ng0ķ@؛Q#_xk?o@LL$iqri IDATyV3`* 0"ed) &O >.FJWdyq#ɮ>64 ̀"4N- %Z ;Xb sذ(k=B^z_u ?QP(@ s|P0P`wIXx{|'tgQG4 2(n=wi-a ToM\};Ƙm1aߘ@JƵO>V]" bdgU5wNg#/>R2vSc|AR]*i- } &wmmm_};7cw.&Ypx?h7 00Q3ĀIg{luֹ^(o7gwhUgt\9nlaR(I0!~_t@KO\NKM, Q<11P( GozG۷>7> _AAڠ_k `L&LwxAGxxtiH {_S&P@Pr;rַmsWcw~AJ O9*VTi5|Znr o?Ą% 04j(@=Z*`0y,RMTZƇ}<;d o^3+sC"awnTC_[[_;X36$%`c 0ث;`vl `* t׷Oo™"||h4Za/DPqJcOƩM?jOM sè9#y]5fcQ ryl?UXks]l >]8+ wI'nWN! Vi=Ak->pgvցOfha/#O?=,&v:PT|ITc4 `֭OOl' 5煪 kA購tߎ0G Ho0$ &Q aDǟڝ4`,|)ݢٓoЎÇh(},sO :j'U: NFb(@&ϚDv/%3gy?vTP 3 ``9@¡o &4?ۏi$,𴝀,3D@gWѱC'_ζDG0<&ljպve>`A_Aڐť*:`fY-џhR 0Y04-'ha À+*jN2%~ @ĕ1JR?"#F-5>am!lK`xXY9&Ă@3dح" lu6~[ jzʍmrN폿;SX<#Azyy Kykc0}} 5@( ^%;;ԼzpS`#O-@KAPy?/6'o2'uVNsȰ߅p1=୸:X[[  0Q`=S˼zAtT@ ,QlW͝ǟX?#=叼) R(>C xx. ¾ G zJG^/vUc_410uQ2B-M`*q@A;Z0ȉP9'3AMy Q$0 6 wz&j 104\rkܸ"vv (`(@nҼ? ! ky?q\6?z̈f e)Q0`Q>K>lmᖵ ;2`Ny?ciaKLecPH@ʖiRQ*޽oN~Bisk>O$u\ 0QOt+:.Z$ޖ0gO8blh 7o8D,GoCL0Gyd]y#ʪR/jڅ@3>aly͌e `1 Hv6R/}Zrm"22+*3#9*֌vy[CLfA آ`D(@AZ*T⑧F~.luCаYE%ڀ}F:jo*Iߙq2LO@AV%}|unq7^{7#Pq AcʹTrN<_NvRj6[8RXz N&}_/xm%{EoTT*]{UigNQr蒪D:3xn \Zn()(.a|#Mn. fc[D66j+QO޿wNucA vm*NsF~8jvd |m^WykHM"Z[jS4^ڷ275diz(?(p=P<@zqg}dl5fW f T?*VC6"B 2kk$ZEm=۵mww;7@]븎RDu4o~ӻeN. %c#HHUmo,~m͋==+FE;!C֐$:J3rŽ?Pá!%_E@ D_w/OjJm],1R(w q17uWY΀n@' r#Ecbgy饧Vþ"eNT@jo(?̶_m al&[5t[ZyWv=v]x|e %7Yd2|ǷRFH@3 U=𠋀z4?A&spx橇XXe<ߍm﷎Gӭw @iN͝c5UHj$|tg/"^A8넁)<*LMf~gLhn^kJ` 5`5в->}?^a\1q'xGKui8"`89vϏp8&2HPqxxlgOyu\TqTC4fQbjz|#Pj%D}–QSh =/DrC~=yl;ӯ,ĨT޿[?r]Zfl&_(~c$Z5 W٫'UuUiV( )lיEvRmsW !l{}ݵӰg|W抉|N7yoH_tdIE3АaH+$w=J@ZF -#z;Ø8uOQanr~pFuE`U]#Vy+ d3y^,morB<'?vT`'ɈN޾n8H)kݹU ^^ҮnI^| 4zhV0)d'l.f88{uCTe)Uxs?#Z:*eآլ<ΰ"`%Łク#,!X.8?YοzOWV6~W9t߰go۰;:X} 4Zƴf)~#R̦躀.jP wC=Dzma ~ƗoF xa(Xӫ,HV}Ξs7pQRѮt&FU ^~D^F5b_ |lzdߋcg8m9=PH\>%р:v&RNŶh gy쟳+vg3 ¶Y,!:p&*;C$C&MnՑa Tk6=o+y^ h-Z@ǁi]?)2c F/~c<e*:WE"\bnra{tF %5J럖 7wۊ5eVD o}wsX@1WNu u tl^J:v *SZmKX\Zx0֚+/zB$M~E9yz4eq䝻u D c-U`_&R+Տi qm^M0zN`XoܲO׮qor]~> ][Dz/{1i)3RQpvB>q7Q*NU RȿV;Xփ XVu$tuܰh: $W_M{'o=)0옫#ɂ٨vDǡ8}RBCK 1_v]Gkk_8?êvuep91H1YNC`S]AtZຑF%р jSi<9>n ՍUd6 ECћuV\6Y߱FCs*ZK |Vǁ Zuvmj;qdq*5 Ri!0T0JLZ`[ݪ*J4ŠJcvBi!xԪfόQO}A0UhgT.8RS_ ٭Sc8Z bZZ Wiþ VHuT*U[JrcC&ox[CB`Wj6`Nݨؑa!30u!X.-MQs_0?)_U@JeqU=p^mKOGsB^bQhg Qɩ:ECVvu%JU }7qA[]u|ej!gOCy˔ h4-B eN@Y蔃}͹^nl[FcA׳KE*  PXI֪K*}#:qo~ݷ25=w};^AwQ.#oщmhF'˳TkkLNWWQYvg{LNj'-S!}BtZf8̟Í]P~hV4o:unϞ0bm⠎2~܁/FYد[M2L&=ӧnO}ra_b eRhF*h>"vWE4G/]tJc}D,VzBgC"7R*Oę>[M1^4qQ*MaׁN~X,v|J,wҩkV-i1٨!Zžֲ< |r-pfL)3ט(':S3T*ph`GӶx A$h/ЯQtSp;!@k"vv8s̞8uc'OVYan$qSmف ^C^oWrHOz+dM(ʼ-ݵh8*O_=cm,1+2v&ܹ`&K2NӞM;lor(⽴gD)SVC.ߟfK{°ӽfr3Ш,TZ_]c-1ogwYNN̝} i$ Cz!.^}ri"Q`DD ],*#RbsmNMT_/Pe8N4Dq@AaYضCbpDI4JJDi1(Yٖ&C&" $Ujy,zOwDCݭusC#lNֿ{7 ;o,C%#2yK E>GjZ5N1n L6˛.xSoTg V>Xz@">>9}T`Grh@6 ]t}#];45z,G }AzTBa[ؽ#UDFa JKѪ l?b';]7vO؍ϑwWK{B ]쩕JEm9&SC4KhuT><.@D\= ⬝ށqfn{́-f2 VWxG67犬V*+ $/rc^/0P+9"[36= $W~սwkLWayFJtfm'&lĈi}[NTgؿs+}w i6_@(Vqpyx0.ZU!ğIDc3] [rb 9e{ N`W}rIk瀓vh@4V J E *n=@tKs.T"{6k qL֠79ɰu3GD[mV股@iL ;c1DIMkᏎ6@90uKovpBĝLN@Zr_zE&JS4u\oU }?6!ጎ I9z?d@:ki!` ?jvө32tn)JSyǛɓfFmcEP2I?<>Nz~ ue)"'c 7DVea"v#%RNnT;0zH]܈f0\-F{?-VC!qٺ7w z?}>G~~"d@\oXo9΅8O:i^NgTo߁k(b골G:}~ܶ@_ZWɸO7V>q۸2"&&y[CY Oҕ,N8Օ$5 ,toՙ ^'OY7\v}s*'R;-C7C#!ߐ-o*eP#tU(˜=w29906FA2?Ѳ}I+ ymip]Nfٙ)f_aR=^ҕtPlW{H婇ג{Wr~7$FZl~nsn!ٮc; SKiΜdp}R-L@x]`kg< m_^׀7X:"C9heZ%o3 3uo{;z /| v(N%4D2kܴEfC~UݯJkor#x=G=gXܪ ؁+6}r8v0 ∜ԉ39uk)*_Eb-*p̓?e3ɝuefϼ0+Qry.-z}\&O.G)F}m6# 7G7w{c;4'KNϝmdbrz78f󨨅Tym8L?ޫ|ֲ5 0+Lz-wZזt<XLp2Y8ڬ F`(Bt : x8+\/@؍ͼƭNk}ݻip$o[HF45,,NL默e{ 4TB$~|v]Cp]1'ۅ_Mɤ \z~#2I1Y[ D!A>P3zdG¨ZrL6 Da͝# f?ϡT*^ =9n۶E<~ԏi-k:\J>*ly'(VָpGd('Cq͢``^Ps_*m7L{W[˅2'mD2ުg 9sLL7-i\ҀGw94o2~ 6J Ssm0yff( YYY`q2asE2N"1-I,<a):!{x1CWR;CP&d@CsPȖԉLN͚Z Xk(Bk2nl_=So'@u i^)쌘>¸M3n( Y__fy*IqH06D*EoɨR ]77 !Y8BH8 ncjj 12V2l_B߁_@'ZGZYK/1;!LSqԹelبRlh-d&Xdl"}H P!5p+z[8x$JI8"VhM!ST\bvz92.d0>q\c ;~= ?͡;C*,˞Of9,qx`Y8o77@h 0#8*&1B p` q8Dd5?YafCְxR` x o'I[1𿀘33ɔ<93n IG[ ZmEF@kRņIvLql8|HPX/a;Ʃ6jy KymN9z~ܲiͽBEc 0 îמG*j-*{wi{N]-  _AJ9n p%2ZZǀmʡ7'[Z˚ jTV4`0RPaeU˸ <y~s$yko*P*6Vm`0!2ac T׀_n;-kbீ?8.<V }*/G`0=aR_CZM}d~cRZFTXDYy` Kh2W= mA*rܸ3 cGw/%5ףM`xSK2lRY5nF"7 '`e aSqg0 EG- g̐]uIaNƲ3g,sL̛k0 7~JiUaqxX1`p]$,!la_E~bs PRQYBP[FkZ4][ 7{뺳oﴄZNd3̜۶m`0X8|D:n/jSϙ-}o#ug 2";ki+)N35lv& Á# / 4UP1zg6`c\uN%'Sdc`0fEdK5U,w#u??Nn ˙NWR,YШUh]DdG?׀{qw0`舀~8%L ᜲE9&O9V]q-pw%G#u]d,+s%Nbz,Bo0RI+W +(-ъqxdG0\׽ ;^a!Dts83f aD~ @}Lbt2} X輝+39R`0i6 Ƞ T^>yqx1`!i r&L;)'m`0:͵(wTxc#~C`^82V63LN4uHq;t)Rc{#%n˲jKliM]`8DaHm asR: !$J_8@{7I[-YK[9;Wfqh0 7L٠r%[h ZxF0\ם~a '2֔(͜<5g>#p(ZSҪ\EZ˪5|53o|p@q]?[SD: `8qLu*QsB*$>jx1w?c v$0 fAs2l-8XJE$yӐ`!uI`I)@\;(5`04UGiVk@c=ϫTM`u]x7AL'3La-39{N&3fK A&W-eM ( <#3$u0p Bdm;Q:MirL4 H%iTWiW2D+k.w*׀?ƘM5"Fq\-_BǞDv!d'(ϝ%_(L0fִMZkA [IDAT4pCrTd2us$? (ZvnVry&fN"Aak-#Vm-UT\K3ha1uW=)gB;S0u,c`0J$_]D*\SqTjZaMr T xd<ϋla1ui)#pc;ID(LFib|O fB (*nhu.ʘ5# t Ieeg--ɕ(APafVm 7ZUkjaf׾cdv 3-=e '%)M&+`i54 H (T,JG4yp0-!Bg@ᔅ9e;¶')L$_(>}WZ[RR2* Iy^8^ # ;u"ABwڲ9L v6vLq!`0Ji&~u]G kZ ~0Fnu'~8d,9|Œ> vNL62-cjK|>y^},Fn ugD -۞"3e;ea'(LX(-٨׮!CP()c*W?>fVap]w>omZSJl\\$ʼn)l0n8*n4"_k2^~ࣞi`aWx/Ipɑ+Q%ɀ #0U_'lo6WJVQ: x660*D ZDd픰ΐ)LR'/!`0tPRM*QR1hQSKYZI7H49~baOt X𛁂eYe=eٙ aac; S(&Lzpl2լWPA %JKE׵kZɼ1|[Þ҉ eX3a Al@4Co Áus$K2nx$E0i٢lY%!8ْAoV&Z>wC$+py઩7# u -p((X5NN^ ,JK %1Æ BK|I (DiJ6T^<@3cz  # Nmw NEy˶&)addrdY7aD*2le*Tmu Td<|xL3T0J\_' (9}R8R:N? /yX`F=)1;HZ Oy`Ye%Ad 82|\.o->$=mv8h!ehNB&*k ||| \?::z$9$ pQ DV3Sakߊ!ȁ4ڂƲ5Sдmhm]iF4ω3M;Rv8P-6 wFq'DtvtttIbH$O|\ޯ7CD"Pą(d mVhi:ڶW980;l2aeĦe fB돗`NC ߷W7yL/I$3SF2SС=e { ɄYm;l*2Q^MxklCs b`6S D}a١#"ׄkI$i0CbaXW18݈J .%̽* іx246 e@j& "h@$]"9NDnͩv%mYNz\#'EmtqmS혈!w Il1$F$~M(Dd.ClXAڊE#shGQkppDC.wZc7@q||w񒂯GlVvT>cb;C~!:II$gD W[MSp@ZtDA0 "Ҹz?(OG~62_E=^㣻O!0-6k$~$ I^o x0 sCb)<&LX0z,/BD쾿OнMlΛNFIrH}' IP"չ0 bf09m}$~m?k,$/O$I$I^M>k˥IENDB`cecilia5-5.4.1/scripts/CeciliaDocIcon.png000066400000000000000000004240111372272363700202020ustar00rootroot00000000000000PNG  IHDRxsBIT|d pHYs  ZtEXtSoftwareAdobe Fireworks CS5q6tEXtCommentCreation Time:11/27/11 prVW chunklen 2226 ignored: ASCII: x Zmz 6.v J Rz ZRl3 . : . w . ..?@J}.*q K bJ .. . . r . sx] 3 |> O v> N O ^ .:{ t: ... C . { m{.J{q J.m ^ .G ^j. ." . .N A y 1.* . . Z h@. 9 HEX: 789CED5A6D7ADB360C76BBAD4ABB9699527AE05A526C33ED04B9937FF73ABECC0EB0C777D90DD611003F404A7D1E2A71F24BB4624AB61C10C08B1720A97FFEFBFBDFC3B7C3B7EFD82EE1F5FD72B9D0D985FE2E9773785DCEE7339D9DE9EF7C3E9D4F97F0 mkBF chunklen 72 ignored: ASCII: .................................................................... HEX: FADECAFE0000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 mkTS chunklen 11488 ignored: ASCII: x }[w.7 . " q f. : . n.}{.EQ X..I%V^ x e| OU.h6 &. $JN N d_ . .U.. ɟprVWxZ |d\t+aֶNi2.+*(0q8 c! *% ⍊ʡ8o@DD}埤meivMt_ jAs%ŸDz,YR)O#,2 • Tt+9j|CC{ȭ>:RPQPõJP8P]Awp5B(X ;N A9 ?1p60 ^AHnJx=o0X 8qp*y|> g*f߅< ! i\bB E| r)Bz!k0d2!ۑ4!3;]yYB_@_B@o[6t; s 0zaX&v9fXc0VuǮŮaep6 Ǧa&l:v6݃-c`˱gl{ {{{ۆmvafc )%-v;܅>_o÷;.|7ߋ7O )~  ?? $ ~ ?/A+oI+q8C>@ [.2~hqI>e߯apqIqFU -nmeՑ:7[W.KJ" { Kv~g/` hS4At4 _\ң"RbmdPqƟ  !0x&4 ?  :66F9d|B~Cx [ $_nȤ tq 2JMUMO_[]).^~P=0{OZ@.]`"T 7W۠:@=% %cǍ_V%=#U w5"2u,Zh/ Vi4j4~Ik5 4ԍ7/-A]zep8,0+*DQË)WWYA! / _= t 8b́>BnܥkԒwUсϨʪE.Kllb(-*HE)yYqIߺuoyJ0&'?ԁzu ދGN6Y* d|~$_̷77}Jɋk)U5rGNF?{P6\t(}^ֶdq12Tį'R=4 ~(\_ ı(t4ß xO__#|3뉛=~]TȩOpЬ kt7tT mIޗFD ,Hyz|B^f .PF K߻!2f9b],WJ+@F-y@/%ۗҽ!-F=NXMHR|DLf< $r(P ݑ5lgRbR"IKk!I4rqBYWtj"l'@/ވjPh'f'u|0@" 8! &WAt-Ijۑ'E dW MjBSϛ!5 u5i%~IQvɌx(%+dr>_PAj|TU<̄$N%kEɵZB@2EBN֤G4ÌtcPQq'L,7#B _Cew/^Dyғ(r&~mc|j|G^^%8YRQ:+%> 飞/9}*9kr.H/8FhQ uZ2 H,;)'r&~Gd?r``S&q•iDKx$HEF <r N/\K)='㱝\z2b%FH_5jR[f6*;)^,OQd'"PǑ%RR7yr#ܘ?EAHLe v2IW`j8x nď;sI/h@l/p`L5~~/$$RP*ר Xk 9): DK(0ѩ`푲J)쟥ZPi3c>'ӎc2WБv"Izlbj!%"$3pb>s@3:w$bJ Ԑ٢LI8~_Ы"R_~QϒOqݏl`_vU^PSRɯ`?Bzq>R%PtqȈ1c %2]" Sȯ`?akuWNbgjOt8yL/$ rV=Rcz>I)}HaY/XJSƟZeI'Oe6$/d4@/t~܎vM?$"G%}Lf.#Q'Eџ_Mn7 ږܟ܇*GUWL)Tߑ>).2*%(O*7>q&z=F4ɝ:$gDIP*?05᏷?ȧxӿ.fNDnH~/?0Ia$ݙ%$<_x&w 46~u*FtD1`&K~/HRJf v zV0@FJk5XYFe!`GkAT)>͟j|U\ t$23ʾS>Qձ\:m2$b]3AFckdl RI6* D GJHYFHqHIl&6$E.J/e֐(i=~l RuOQg>ѯ{|??MDBDP"5>^R}kNL^Xh9[eǸUoKTƂ>3MLi&x/"5>X^V]9 QKHF (:vi$+>2k_ȵ2Z'8e3h7&,υ5mCA=]_*<s8K=^^꼟o+h@dHtJkY(>JuPƷ6&hj_;_ {5*A alEM{{6.`;2o{8s8PJf>$v8^Qx܀jQw?p\ ?JDn~+] '/L zI=eҽ`F߂ҍ}Տ+!Q'N#9^K|GuM,: \ J+N>d|AQE:_uR /q.rzFt>ϲKu(eOwTG&8W?M 25Lԟ)X{0nG/mm4Dw1|qr>_(/%ZȾ oHO&TbMt`&xcK`&,&8 NiMpjL97 9\HpVkDu$N.&5q M~^ P\{Ny8xc%< & Ebb)hM*R V Q km^۶_ہm+Vmhx3Mi*:z͋.~?i..>}HL0ɑd19KNjm=Y{>j8kC[[{Vnnn`kK ,ly{+w?S?94h f0t3z22 6TFn34x\u7 o1gnppppdmF16z~#eXlkgo8xqqqvNf>~A!SƳm3ٙulw7;f dw;2rSl5qkL%!2S4444iy ML_2}cYeV5f4gufh.037d<\j0WG6?f`j~׼ͼYtN>,7Zn ["F4KL|RsՖ-,͖O,--,[|m9l9k5[VmZ֐5Z`l:Zj-[#GˬOX>m}uuyzFk7oYZ?~azi[Ѷ[::ٮ bj`ka[`{ʶɶUm_۾`;b;e;m;cϲWػً=ۇKer{>>߾~~m?l~~~ҡuG:(1Qttv;9&98:s,w~q'9N.ή+ݝÜ5αz8'\yACη8snwt6;u~yy,ǛS3.g^✧rlٓYќc9?9s:lsr\ *w鮙;]wqw-v-u=z9jZGf>~A!/]nܴ;vs!Rwqpr777_v~߽c'O_{Pi=v]}{tl_*_/5k}J}er_M-={qzF&f˾|}>}7ᄈL~_˿пCSSgHಀ>` ր?)pE[{g4P 7p_k[vS,UHuP]"uՋ*RM fj5NUPHj4UEUST@n&QSOMfPnju/ZD=@-VPwm}j7u:F)8(xspHpX`E2828:X ۃSS|pzpFpVpvpNEK . .>\|&2*&.>1)FMOS4Mt.;ҝ+nt݃E_C_K_O}~t&z}3=FJ+Jz$=Znӷӓ)T3Ylz}7=~^!4D2BPPV1dBΐ; Bt(/ĄPPPPQgWw ~A[BBeЈИPm>4>414%4-4=434;474/ (XhchKhWhD.[;?wE˹ro^F9cލy=j|U>ߟE~w?!G'd <,XXP# gdf31?0G s9Üea$ۄ3pf]8+ aKvs7áp^cs/b!e`( ,ہve؞l/7{={ۏboael;͎akzv<;Nc3\v]>.eaO+gUZv=}}}ʾ`?`! k[({=ʞaϲep9t\6g欜sr.Q\r\G3w׍+zpWqb/7 nr\Wƍj\=7Mr<7fss"n ({[ɭpFn{{mvrp{fn;Nq3ܟX!Qئ,*Rh+ ccKreVxEՁe]6օν}>lch˅HmkBF)3gmkTSx}Y7&owm-޺0/:gf<7X,Kծdْ_|Z5%_IVDdL2R$3 _D| On0/\\eo_: 2y:-/.su_>=2e<q. s%-ee^>kMZ֏NkzۺhMG'c~Ӛ|9MнS*{.ٲ= ajQgx@ {ؔ)}÷t?`D:7éq؇kHGCU:a-OVѲ{ư:3tz#Wl\͠6k[JKhhYnVVҹ2 Ѡ;c-=D B3- I]O"|||2i'#-WJBRIH* >L'ÙjpxF?럠o:=JѶ Z|2 4SoL<Zp/k胒R_^o&ӣ ogіaGJ({["J͂e}@3%l$7&"f.`']_0N/*K@2VbGZDt@E-ĒMQNT7 u*tg'4@_mU3_]Q=` UJ'uTq:ex%~; 5TSMd7{-/Ƨs?LZfb&m$pI ߁4_Ir!=,XN'<Y`$$/Gx#n-1P/1".BK66 u\{&x3 8O[+ϕ\]f8ՠ򔇏= Kt ѡl.Q#3y J<%x%DI+QCY ~,6f4@ʸs G))J䢕]URIQX㱒#"84@y\KR+^5/~) 0Hl2{VKh'׎d`h AʖFٞz`~AgU3ރEtۑ,*%bo.a}_oIN#I\rz ɻ[$X>;i$DJ4MD)DRlLX60b%J5}aRMbb%Xi\0ܤbM2IDb!> Qe<fJTr\fbdBM/l١H%F@ԛȪi͑Fe/*3" mV4VHd7*F߁L1=++& =UbyX\*>?1``(:]W񋙗y \~TdOGAGbɈIJ};T0孏c&M=bȊx;MdpآHH)Z!t ?Mw_/3 ~eq88.ց B @ ; -*!D8}+ %;bVlW`]2Q%P=|>a$zk1`_-_YPز SĿuۂ'H>,.`LM6sO c%ȱdk׳mf ,ϵ'f&I ?+j?yeҴd$9Dqd#eM%ߤϪT@(a3g$̼o/RT4/C>U jUTQy9 *& Ian%W&e!abp ¤R+5WcLՐj&z ($AIV[߻&]DO3iKRlk72o`0R~25y= a$:Vpm/$jIU񟖩4Xrͮ:#OSDa#3g㛅8&rzƻ>E|%- J{%YTTi))S $Ŷ^y!IdIWZMĦ ;S"Eւ^p(ZRu4>!][*TmƳ .!rzāc* ##2VsJU`nUd %I#0_KwŖ:gXE$I\' V}N3T^](400%T> ɂuw[~z8'`Fä:5 _A\Z&T<'VzIVC`?8i.Ŕyl4Vf9 ϋ52/@zvmv:\"y~ce#D$4HH$|0/}LbՄlɻtgzo)eD`䈟&NR*_ŮD[ou, @戄HXls,2z4U3e/{N !r$u)iBuHOaCOɡw@Zt@{앣8U!FlNg7٭J\U ELw)Ouig3{; ?χ}܊Xe3+x9^۱s V=]O La辁F}BuhoBw4F#Ӊ_]Uu͹5uXu:a1.qr(Ms[ցRul: #Su;9#sk5=CϮܯ1ج".,|Zz-n: 'wsZqjywTuZt:Gb1|uZ:-k:mN\wځދW+kحKsեyӥ;t駺K;4]B)Ơn5U5mkzP8r2k7_qՙݺnnt,9_x7[U͛NB_͝A,;rn\\X{\hӼ5iuG^Ԟ7?9[%fe%&o6RpHx^ںj9os+ %UZ,gCK<3g(q26gD5|6YݺX4.K~lI)oc<3AW:R9"Y 6x>N7 6&FVݚn5ҋ{];֋^PSJ!]u#1X.)l.|n2O#I3F^qǺ xMZgzO87be];K od8 +W/'rXN_7syנs!qd5|йɼkйAvɼk> ty t!d5}ɼkABw*R;*^GE1[ޒ7kAZ.xh]g]V큺B`CM:ni:F @|+g鉥+g5mU>:]Kyӵ,XֶqZfmL2s!\$싺zn;m=Wx·}Ƽb,Cs F7`͜`f~ӭՇOU t/-`H}sAwo QwXgsB9bP1-iMWg}87뗷 ɺk?xƂjGxDwEІIP۟Vngg ݎ4ZwВPsv9kfnoH_P^oҬY,i 2&NDw5ل7UFX좁Ռ =$\qlXuY-=:${u)|S=[bζRF}C9h Ϡ)j]N^l܌D$aפ W֮{StRb Y#$_>(|a x-fʹ4&@#ҧЈz E?j;#|5=/wYi;'}^)<-y~1>]M?ۯ7Ee$YU/*P/+%G4֟r kqUC}^!OROzhI}F2n]Y!67(ݠt J?ޥ!{!>뉲fE]~O_>q2}81-xIGOI:祉ףmBĽӶEduAaz|vo}o<կaV&-_gW~>2(yޕvN~/I{^פ[zdp U}yE:Y1e:sX5!hf2+*?U'B]~h4ێmf{aVy󢗿lC_aX/ݘ<)%͡G]L3 ~9,7k+t>ʘ%_PPD /hz5QO(۸ŷ!?sJJțTYGuxU+B_P/Ĝ ]\$T>"lJ{'s9y V侽7RB471 !u qe:^ [?pe/נ^K]RD,FP>zz 8KdnC 65+F=;sמWꛑP~C+__ /2s8WoW׀WlJY ̮сx振:MxPH IMʘPj]XZP߈~K\i&qz\]}c+2-j/^TWE9e3O ]{<,? [Z|*K;(-۶rC_5mJq,תVYj;4w7ۿ^bV(ǚUNF'+on:\=6쌷sgDbt$iAT\7H&c9EJ N/lj9*?k坿飻g>mUvn{)Ҿ]{2玺uӆp&*Yi;ڰbpT>=!qGu 7XaJXѶ R~I^ F) _śS=㓗2 pnWp-y>u/nZ';|7pAIb#-pوFb 3,3 e" w<ᤷ6*8o]S~kx[[= Sp o3蓿REK1Chم;v^lv}BORf;>Yom#g7f>hh=1{Oܢuď-Θ)YPl+s<ٔFW3׹{s8OByCoD'Coo|qX$2xSߦ߲/ź5REgQ҇{ U1̖2ZvN/.b-;ݣK윜@?^;ѵçtӏo 8.ua@?=UǞjU+7%:Ta9zyitݜW߾d>?@y|8Zb&+g)hyw2&?HO}@kɂTFezDp;ПnI(B߶pL5ܗM턻E|j&ֶB&nE/( s#?'+]c}w;?Töd,Օbm~V߶"=nѬҖm{kD}Naނ\~؄W׷MbTkQ[^68pbϖgH ӌY_/.Y܎l@F\G4i)I{˓(B0LI[x0Ƒ:ap7cJפQdp Sp&IڑH$=a)z׌.._\J8 b]Tտ#-#?쨃`W/&Uپ Nߚl'GGe"+Y>~&jƙ" 3LY_6Lq鬒3lY$s4, gA<-jPAgO,kj45mF((W(WhLqr/r85҂M(k(sIGMKt,=5ikУ =zKJ;z `)RN"1NBǑG!YHL2ٞ}g>w̋Ҭ],oie=W<9 "mKiYj5l瑑HVHx>3Yuq)@P+ڌ9yN=;*\-yҎ"rYr {r@nDcT Ш/ž1CDtW H[[=,0&|RN9gvA G2B%R@z~.(8bҌ>;;y^# /=#$<1G,kaiΡ.mR^oE0@=656 EvFEbB ,]gAdcQ" 97d9`@4Ό~GpYaF ?fC-32 bp F)ti8G\a HGpi=H{2)S"V XH$1^#:infj-m(Ga$Y1 EDd)}:B"~98 yhh2ˁ6Dx,=3Ɉ BP CHMo#Q@rnTdA4 A˱LD[HA}QNő]N05 ̣ ~Wdc+ zu]NSyj1YhU$T468SidH9%B#pͱ)%$(-< M)%Pa%p4&6\KwlFc%x8h3J H"Y7.یSn;a3J/( [6$$GQT ~6$ ,'IaJBR=6$8:t0P?&R1* 2_Q{6$0N$ wmbIH 1D*, JB /;%p{ICY1Eo fD 5Ak"pfD@fCIj٦DYEcyZ)o!.Ԓ@jSI.h o"02ybQ72!*)c(ț̠r Pf6jx6MiEY R?iK{y6$S"a$Ēx`>**MĒ`$-H<& 1*hwo&dseXiqĒd8DzB']lbIb'iQ#I W  򟔲y%r(`& }XalfIrx.si6v:Ir5mjI݉46$yN$,-IS,3C?`&$' 0/nK"3򟧷%)pǝ414L@M/)L_{Bx6Ä]iv\b‚rɌ^'&(ƺ}ld‚0"Vx,SeTm*H*Q؇t&w٦^KE;#@[RƟ{(ުHw%["=+rZ9dye||7r&߆H :l͆ qx$L5}0?B-FV-;fA. #\?)sG<#b~(Lmj;X8HV69,R?i"=,=52?q"0#-{r=~d~@1){rcd~:OENTO~ø'7N`X}'#Gd~(>yV6䄲mK1pOvl-4""Yԓ#Y6mgԓ͇x1g3O?#(GBM=9 p)zr8,wP% zrH5J Dla[{rxGo@8aB;ܓI'-S҆j!m Τ"fDQ F/!(N$l0rf$ÉLz}rA$4g>S6'G0kO\z&i'w }xDά=&qO.YL{2'bÑ3߈rr6Nb tM?3'w¤(K|rwl¶< 0m#|p2,`.֜''O !Is\kINN&$2yYNSŲڨ1-rҜj삜kNGN*N“0Ԝ''0`;9ONrc9YONe@:iONgd;yON>j(7A6 `hm M!0648#6&YG6f}8l66f866 pm]$" 8r9k>5gi % tb"ZgeN' vV9+~ge)p9ge+qV9B#ge .rVK5gih0s9(Hgi2u9+=XZgm47v9algm>7[w9+~gq9`fE"6 ,)(gy@9KflY0fOs|6lY0hO 6do?'k3OS6lv0gmN6n?oN$Y6n`SN-y6n&bN6*6n9cM?<6t͈>g qSZcSfͥ6X}vޓڤ9ʏ]vH4͵iq-8JEj8zמm'YFES;a )WDzNxGqY6kyL0_;I|| zfi^Ti6T5 m)_QՂdC ՂعSĄ7\OvA cNi҂E(^C9vgDQlKHhř`+ng gc'q/_bEycI0ɂgBdK;3F! rs8SF~u9\>No)Y%2 M~7c#I8":ʱ8NydByVK@9vf W̿&P\APtK4 yTK2@9[N;%U6̣uC[6qUڂdeAväŚʒ0(;^Ɖ@tvPBk hg`Hh TD6@BP k i\{(JhM Ca1?adӮ=LcӋ@h|Gҫ@v=45ծyvRn +ΪJU-jk0{%΍ =-ihH -{@ B{Ax>#jCI0˜S WYrn<`@0E=t H'@;[_+_p}S/N/QO9ɾ|@XVGTe :ݥqwaFcwCKw?k8MlN0m*L+ ͶA̝oEvo `3mvϟ n3|lWOͷ(\ĮMP|% ܅_my( wӫ?╟}hǠ;qo)nHAWͳOQ{_~w4Tߏ|{Ho 2iBqk]-iNdHל]U1(z'DZ/q&%ZC)!E .=ma)<>lHSQg=`wnp&K^\s>O'36N.hQ%_>EHm\n `xNoXK)OM9^zZ|J/^[ߋ)bVN9FSQՔ#cdM9=dlU3B&e梅W'^A{OutJ:WWFWֿ==2вLj%íZI nKhv1fW* I%!0/^;쟩f gM2|e~1]AQhr-Jz!(%~c也0ܧ7 7bƯߵ@RVa2=j}̫^GJ(o_d)pNh|7.*ͯɡK~/vE )% +[&+R2nEX)ʉ[@u蕯8@_mU3"UϮרތY{*:*82ՒsͻKzTSzKGn"Z^O+%~: W)/Zfb&m$pI !\H˹0 OAئI>74rKK̇A?ͨ 'H;מ ތks%3hl.r5(xYɖ١;r>gKSZWI+QJ5hcOiᭌ[?p$bH.ZIU(U1+9-r#Mȵ$%ZIꗒ0 StP!K+jzrGưliW. 6]U:=+Y{@WRRJ]"&oUN4q|ɿĵA!EbxScMBD)DJ4M)/*UHƄϾlXI(VO)z}_-%$X)Vb+1ƕNtBހu?[o|\ׁsɆ2rՎvg3%L9ja31]vO`zVk$# rdUܴVFE_F||Dž6+˿MF$ak@&ݘ*1I<@ZnsȊg)t0-r, ZXg}+꼫 ԉI8 F X2:%K g*Hg6R$Q#щ2$CdfԱʶۈq%!RdLLJ\ u~m9"ek"LP]%L5SfJS-ia`>X\*>?1).Wp C݊KEWHDjO*كD#IZIRdQ}Xq*1&قb1ldERf&2LFplQp$$@^u:;⯗bhqʉ ~8]u@v҅bJÁfy#(۩2 lEFE]t23f~6y] U -]q8`/A8E[-y♡:Ԙ" ͤOl37V+Afkv=f@\ {hzjtЙco"#pp1P \+MKVJ3_MWa> Jf;RQRMJOt6sF)E՜Mc?L!AB{ {\=n^VQ^%Ab͐4x9/^ zZ`R*X*& "L*ҩQSx5F mZ (FoЌ©IከNdukuYMdy8$/Aʿڿ}#á #'SJiFBdXPi*N%Z:D1E9*L1s;YhAcr(g`QTW >TO QNN>Ua`Nn]l 7Kj͜TN:tuDLlʐ30*Rļa-97n'Z!-YGűRAEf<*"+9AxHn?K-=2"c:740l^VE`^P4{WlɮsUDhqu/iG4C UK Jo(ZJ3͠,Xw`|1c֑a^o4LzN_5:W[ӌ Z&T<'VzIVC`?8i.Ŕyl4Vf9 ϋ52/@zvmv:\"y~ce#D$4HH$|0/}LbՄlɻtgzo)eD`䈟&NR*_ŮD[ou, @戄HXls,2z4U3e/{?߱;NiNgY~ũ m0bsT>snUJp8,j5gZG̴6+M-xs>M[?ًw }U|>V,=JD-K9_s\tr؎ESD}M` eE 5|fD};t )4YN說k=٭cǪcMбqEi;rneӁW#ޑoƈל{\s%ezv~)fqg~߭bkqiW>ҎS ̓7[ҦӮi=̬~߭2iYi;t^4u7U]q^{n].͛.ݡK?]ڡط M1_w뮩i];t׃"(đYϯu3u`[~:m:mtbj ~dsuBuªBIK=y*4{.+ 7a}ַC"m֝P˩}(b$8Z:9EY+9$"uNqYjcKMW;և/> 4С$ȩ,JQqIQ4I6 t˭̭^߱^|′h~R) ]Ųp$Odp\.Mi6 w`K7uX7Ժ)C]L8<&\+sDz2~I፬<:9a{W<J٭F>L">ckeg 8m;] hlI]#K7 a7{Jm, NUM=䎥HK]U@emdp^>[@WdQ;[xVXIF8`%7WKc)%-zԌ\PM!1a-I֣C<_,0p|˽=<+gYd8S0̲dd笚/flG +/z9`c|݁8fn^,'-[*Z ZcVk_蝊Uv4t#e+I_o~OیDӕ) ~[ZO}jckL?ZuҕRw?`?/]m(v'm=P*Ī#lO횗wǴ9wXwS7\Y*jX^8-yI|^ _̻:7w :7q|]˂u /m_hm+Lr`7>$uqYD{Pȯh՘y GIeti/fйw+lʙم_PPD /hf5?QO`l~vŷ!?3Jț,fLL}U+Bw꼞i*m~]޻WCo]䓢fG/.]/Է\'{]X7׍i"/Т6aNz 7\c豌,uIȪ,t+i.fUSvfp[~C}ay꿔_֟,3}㽛 \y]Zo,(F!03mIp/HfHB-i¶߼&@ | . xqziMqF~h_A r  )8VNv׷M1A,hN+mhV߼|רgוw:O 唿*>ڿ9F b .a=Hm \-W~e-iP,tN0]L]s'z'̓"zWy<oU{KóV~mﶭ]rxSy/3=YY9vJ,[D7tf32Ǯ'FvVqLW1W_+M ۞skm1YmYP$p}2#ض[zacC8+}] bOsV((!V{}F>uJ>VNP,[>h;zko'i !wV~w,uo`v֑j/F#9pgϔ\q0.InK768gFePY{KQw?諿腎 Wiu[fnR蕔xȔH7![wtG$9e(gkrJ>E*iӯCO{%~YW-s4,Ɯ0};&O6: x;BXϟ"uӖڝ^i/=Zb zy#/uIœ;$A6lf_mK1Kraj,6E>%np8spջP8FؕBeo|qX$2xSߦ߲/p{~b/? b5bb~f.WÎi`t&Ѳwzq0l]\e*y .a>C~|KS]AFtQT8_\ɲ3Я#UhrS q3vqc13AAa[3q'_~:TzX9ߨA. Թ ѳKCMZZ }}ɢS} "pu/.L.(b=]OA˃g6ax1×SzOa!~}?V!6zoou0bpeo8BuqqC?Cf u}!IX1v<oCtDth.Z#/g'Tр;K*x{'%sq,PsctCu:TYC\NQ{<<Æ~眮w}랼<Q;^H9H˓Si\ON5& գѱ:2\N>Mp4'i|Jcң.|4A= 5^?.>{8!>|z[/ FFMΖ$'tQ?B1g#-r%J:"I|y3pq)xl*Qs.`}0~w g>@X #U)|yDp;gf˿6zۑn7 ng$0 }?隘nm'{ݯY;^tǹ#iettwWuw_?T ܫ+I3ʢD-0wmMpwF~5g/`-s/ Zs_õtEGJJy>>~&jƙ" 3LYLq鬒3lY$s4, gA<-jPAgO,kj45mF((W(WhLqr/r85M&r5fr9f2?Qӣ#:z4q`3=7K H5hC=Ά_9|篈cTHfLq$yCH%R*p-pqtL}gg;;}𬆴X3g`m]` Ԋ6{N^SN@8 m,dZ_tI)'jg"MjV9y;bi I#`qrHByIrrD)@$"dr˘A;t9)y9amݦ'ח3m%x.G9fykb#+3ay;ˣ$K˙Z e,2bq;i vͬr6B43@cРbV1I[ b@=JIAh)G>cGV99t:2~3P<1O9=Z`$vsaiEac PpMA`%q̈́Eq5H{tA@GacPKgXcr΍-Y.GA6'23FaV`GQ+29G`Ɛaˌ"@%<=iF ݸhZN@$"m0W4+yZ!#^LJFH£>67I׈NYZKFqs.Iz |iC3D*hZf t¦sĒ^FDī+IH 36$! s6$#"I ϟ-%@jCp ݛ%|8|F&$sss6$NPGIXIZ{h&'l^I'& %%@3Vki;Y1`i^680͇ɷ#>`BG(cN.b#>-l!Hq$I?mF yOl,A ηY}jP" ˆ'!G܃6ψ8Eh>Aڸ{0469#R?U> F;OH bsOOH \PmLr N0<<01N=y{rP3ND p2fi@a>ppFd3!#up@"{dKS(ɧ Yp3&fy .MęT>33?I;W namɝB_)Q3+gϦI\cA:rL@:̓D;9]Y۴F$:ۤ99=ؔy8'؄M8xgt&6]q3"mwxBn75=5*$M WL WL|β1|>nO1cM6Nfg)=u!̞Sf^-}vRc9{ʏcnhDiN{.jW;{3t6&v@}=u6uYiK46T8X,Zۧe3dh4<ϳ]$Ұpa*7h8w J,(8F=_l_j@S%ɆHy̞vE$qw< $V 'x#;.7lfytl74B*uGAm9Ie99<k3䵫EZrbP$]q0p|JSbzFxyz(6N$oʱP&b /E„G$ø4_u;K8u;\9W'ɴG,2(Aq$L4i"_9Iֆ 273.b]K6jq <_أ"H%f1 ΃rSR!ׯІ!2RP~ *8ua"R2 }!qr$PN얓'@ p-;t֪M\a YY0ر(w&e, zƎqb@6=g4Zm B;#M#m D;ЁЦ6m,kCAW.sSPv&(kb P_*]!MB`/]fԮ?B+$ 'vpk˩E(Z6̞8sclhK6Rh6PОFp:7OHuxbx3 2gTFif<`wVO>8 (Ldl& 6* lD)iTSN:P+EQm1UIN_we%ew]WRr?@q?ۣ?sg,Jm rmPs[ݛo34d'H̰?_3Gm./e!/yk6_/G/wW3e[cʬҨxc`1by!F,Gg}jhD۲s6RrХiŠCjoŞz1M|:n[Z1i 隓c?b^ݚg19xNLyL=/ |;|y} gC:LmkBSx]N0QʫajohlU&fnÃsfd,>Op3]E1T яjV2ڃ-p uyGpodl`d/e>v 9]Aݩ6ٞGÃT{1H QlH':iуŏpa'D:__CEymkBT8x횉m0]HI!)$FR?6c>>~sm+vuՑνYu8uN?WP>1JsWiV_uKEϸ/rˆ_gKW]ױEYcl,[TYHT}xL#}A GV7^}>iҞ-i;}LJX&TP3T#ߨgJl e'=?͘ona|7>?ǐU%;/mN/IfQփz{G}?v✽3X~j{zTAO^ʰ>?sy|G)PȽ^B_04B_0Kp&qa3 ݰ{ǘc|r쿺LiEh.jEq_}P3sQ*_'WfmY1y̢qSű { ځܐb= ڂd&^¼ }x\`! y+[K:sW,L>s)h^5 gz쿾jOW`'Z :|_w \ zQ'|S` y= qVVȰaN. y!/]5 Ev xmC}`m;[lPXoj(Y:(53xR!޴Ľ?DtxwغǪ[5?^O͙UyÐl9op #bhE>:>3wiY;D|zFٿA5p?2cw2^0*78#32!U9Dܰ4j;W侓0o j dwio@ 7/o `}B /O&=r?97 | ӝ/KfIYIv7?[7d6oO6/(Olomuno Y'/XS|'̚v/0>Q|P ۄ~oY⁩7Ypxs oWJ]Cq^e Avޚٿ>t J?׌| ؿyZ?lؿ^0#Nf4G:r?_? K*k;[ؿ~7`;|wI47 %ٿyDkb;oG Ý<\VXzc/Qw.?mkBTCxًE=B}>AADCA}i{gٝkfʺnbLš7qQQ!x"$DJ#G&́I߷F"htP]TiEii=hD"N3gq aw|Ze NvYV_BchJX߉ctqtm_y?/CwrTͣ`ZKC⽗evHřox,9!;nYʣU8Pwcӿ9 3#g> ӿtTng F*cS*ww=~[Pˋ$Աp&ǃ.{َZ}AQsk{cr-AO`3s( N(7yp&>7GT<*= sɞxY?w˞xxcnq3{-$ۅ?`n =TeĞOVY~?hʞOshrGS={ɔ z@SZJU܎OD_^0{,Z #^ p?S…&:}<xt_Dn S_gƫz^㰎։{]ӣ`joO {RoC]?szz7Npvy߱tf(T-}5_aᢰYᕪ%3cFs=9jݣ_I=^7?6xok-=?&\lPOv#C-Sݿ/Ws;~_&\%Hqύ{;Zr^9 |-c.3Gn'̹oN[(}] di1*ʨqm2؄hm$TB\ u= HfUy{lBtz~0~_Jڹ'D ҾXGI,Rur1'Sp%iTYUt-k= mӸ)D?BڿNtɴJW6*D?Eگ cH+TiX?c½RArBqcˈ¼p]b Lw؜ОX@URpCp i7ŜᑶFMRYHu} @\j,#/6?Cτټj*H3s) <=?! C|?TI,!-}@YJ~/&g;D3Tʦ?DFj菼{ĖC+!InA#j0{J njj`3 HEN oXJ/ ~!A Z^Fw c LssXpz ?x/>GP3v3ܺ/3kX@k@:?U@{$7 N`|z 4z?ėĎC'qBZl~5񒻠?8ief?6} UcNl5τK,M\ & 'zQ_@RrD3>Ciu-)}y}S{v !)y=|W3Ujݓyӽ(xHz U嬟[(9tzɐ'֌v5Oĭ E)6;FIKk o1Cbn- 0gܚ0}Ow u\Frn ]gzԛ0WNq1W tL2D%ރ{]6&XW?L9ҕhyZj+5ourwAθtLUc̹:_|7gޯ%r>3?^iyvU,zO 8~Yuqm=`{>XdcCikjH9ݻH=Zl g8;B I reJՓ&y-^`ₜ׵MΈ{.x G{];mYoI ANE9^kx~iqr_pU)A{⬈e:o?o n%ؚfMTF{Ns {Jxoh|柀2z{s\22O'sB'c9r+9' S"pύ+g({NXecvuPO4CxׁԌoEpχ 6(Qu &"Al:i@ypIZb%o:1 zPG:0gƬ9/T6wO{`䈷m|8q'nBMpd7=Ӫ0H:7gw )"^t>1` c3cc-L!X H\ iZqċAUDgC.i1$xuc,/t_{<"~y[h/8Lm7 1zpA89 %Z JWZ_u_\Gڏ~5Ƴmz͞tE (]֣֮nzdj?oǑ4'/pEAAAp             +&yAmkBTxUU͂ )PPESAc؜:1ܹyzI@DT( f,3Nis;(=up{מ2s)p[KK˗qljyd+Ty=K7h1{IrÉ'x*U֘_N׳x@T1q [h:N :C:ObJq9a\,W1'!.?4#3yqZYd:F ~r8tyMgyOdڼcrO+7k?] ǡ?:VBp?jCL7~||@z_^ӱY[¼MOSy/J5}, (?{X,P\e;Ld/Q/UMdk?}!߇~m=+LsIKZH}?zwk .HyL阀^=jE" oƞA1Y_w}b Uoun:& ~qگ$sw~=_xLdyu_)Sx,CzuXG:F߇|ȹ_;=ClnvaƟel{_1ex,^EkMd_C  p?nx,,4 4}1c`r>%`o7}Zw A,[a: /-@el:>Ȳ V03[0eW{x,K[y^msmsult<@0jfw_Oo ~uMǯ{ y Mdo |m:=1YՆf9IKdLd?@9ZCTXC#:`}@Z*[ N` 9 H3< g[Wĉ`@k{tl@??:W뽠.ӱY Z;-Ȉ<0=^Ko=3_}+<@nAG&bJ19Szw#xLe&=@mE LNrw0'ϟ plt@?S= qމs?H r8q/f14\?=@sXf: Xρtޛ?x{vL 3#xB% : W? WlP瀋|p۪8D5rUa_h:l:n Y|sBo Hp yNd+gj݀&vxX k<+؋&g2o!Y\@7rd3/10/`N`33YLv.1;UMh4/iZP Z|`w;"Apor'< RY5fzG? T`P7^~(wR '4/~l(mkBTxyl>v^{ym9}WBBHUREP RT*TjU*iJC6kN9\Ƥ, TjU{o73of}O8ޝ|u9CenHU}罡O"Ǜ]s؛P_ j˖@?4p?ֵpDnO~*-}X &Nx#{,e.}gț*\28πq6IwH%[]nֻ[i|G Pkdiryܷ6nj;gct (\%V;ڑBkǀܠ*|׭SRZY𥗁5:9'1ddLkCڅuZ׈Z#֥ǟ *~,)-))z*^Al>u MßWլI2_ ϱQ:tk ?tPc׶e[_WU`vl[k-k3icc-S0$S#iBT%~r|k\hrW3x *fx[kMv=8sپ%4&XN}t~*{yI!%;&)-l؊i~NRysȧ [T?։r@wsdl5#غϵvgcn-og  ܲoy?h>Vyɥ\o__kJںáxmc?7Mz);2{3ִ`+G ?ъ4pWu9ճdNV)p=8gg:k8mHxb)mG[$rC.uQ|l Q]풏' ε6}27-[{r1J}؀V:t˺޴Iw395殊[v*s޵-fON m-W.ACpO?qQ)U~ _2wσ ns3yVקEUQ[2qCݾ:2kƇ؂:16bNں}=-El^<>M5lv}\ZC_ '<{@'hMqg[{|^R.O宵uٿg}%'lu_ ? vp_M7u=>5.YMՍ ! w)<slJ<֪!ieȷ `>*+-+彖H>א4ykl${l{6#k8hEod4&I 3pgz: º95vy/Bg͝NaB^'wi?Uxxa1^oKti)ΫlbZe݊{ݯ~,ij; (mᧈ:N߳O(={_kXvGy]jg5Gr{CjY&gݔTөm$K '*׮P ]{ 3y˂`sA_AX_r/G#R-od󊯧u:u~!15~^Cx[v= k+ɽ!͏l ?wkgyyJmGx/Cu6o}쑿=,Rm'z'{V6op _{" enю4牱^ǔ{9{Z_w8=a%}ؽ:hF.>kf':<'?I}=}v~>Wd.(_zDG>#e͞}/51.2|c4:za}YgN5gOsAgog}|'=7;9`&YK͉HZ~>-߷ ga;䎺&3s`:\-h3zRGyT>+{"K}>|}}MG}̝ۜ}r= ]47~g} yޖ<=Ӝ/նO~l)`X6kf+uYؾINJ 3x]uf9P^3HDpοg:v>VyP[~s>˼oz ܓ:f9OWs}kP^3HS]j^`vq%{OpLkl+?۷+>#YM(y}-?~f2YU={|>˭{>3rLEg~T}O~;=)yX{|>|7 .^3ȁƆ9|yޗs'K!ܣ.ޏk6tߟsCqW8}Vz_x]PuSCF c~+/ҁ~Sߟ~_v^?)v'tHy~u쟃{@"{ ]ʥt~0ϯ﷨ &e:ruw{CEޟ3W qg$u_ObO%]靏M pL Ǘ[$9J߰+۷v~9D\U]{ϢߟO~fΞLm7'P>9~}w/PX{ O7upOK_C}9F`&B =g=Bj|jLzhƗV~%C+ND|rϒFqzpiM?A>Yo̐;3f9լEm;XYȣa%3Q)>. z+ϢQLG{~/zVŞOcwy1K!OZr%6dUzﺜ3?!_u3o۽/ߔ[NT՞orew|Ӓß|=cز<ド|%E&_ciDZ௝09O{!K9g}Qz޳kgI5}_S a<'q3r﯐z?M{iý{ ϣ5k7-s2_7ϫcYM`3_szy|?|/޽_-:GLo+>tfJK_7?m0?/8LD_sܟ?oR\DK]NoףXSgAxsg"!ßIB~ Mg&c o/}g!Q h6q):6)sx_3 ~О&߈&2{]@xXWrOCkhV_X_R޴Toʨ<@~JmG+vWWepW>mq$ĩ_;3Ҳҏd쇼?LO|gYAvO_ 1d"gQ8LCen7+v9莞g=ɵ>ò'~W/ҞRzYt;+v΀i\= SnZ5g P0́mZ<%ﯷ3`52\`"_T}U^Ou+V? ͓> mkBTxx흍) q ĉ8D^>׻gI@XjjgiЃ`0 `0 ?ϟ|:seQ3|ӧO|:2|.};7eGFO6_Qv]T]^ˮg{>pjzkuo{yye?{-x/ D:3D&򈼹e^Hyi#/OGzϪ߯_~ :sMe#M3Y#=2 QЙ[\s=E8}E>GȩT ڲTg-}VfoSVwzV}./>~!?U1<#}=F[ ~QڋBN..+푹^edLo+[\-k dW(}6q$#?z6Bөi?L7!3O_Q}Пuo[=tkȋM!'}/Ƈdr2_Cﲨ: `0 :8o=+8-4}۞cĥXdq{bUq©ήm!ƶg*ΪU\z[GA=^+ru{LV U?)V>ғ)x|Yҁgi\yi^cUo*= !TY?rfgWsʽVn*VX#=Fϫ+[F~yH\L~[O҇h5ݵTow|Sfӟ+);F;:x )/OS yUo2e)Ve3'wgGg=J^`0  ľu kU,Ksؑ5nY,bXw{ w&3QהNQev ]ƷgcH˞i{A3I8hwduwUIWq8I>+@pQşGcZ\ƪUߝ]/:3d;ɫ:gB9R|GW~w2;fzt|+i5nΟgZY|<1NyŬ|E7k?z/k><=Α}N΅>uWydʬdz `0 *\?W8GY:Dgcg< 2+'W6qn؟{ru"wU쏘~c#T?+y{Q,,^qF/Xv8.֩g3}ȸOP ~n%hUG4(_sn|W}Tg&x^c,Fѭ+ <#+}/Uw8BRh_|33!mr\7U9m({ѝpvew[xG]߱?g;,nҽow8]וb?OV=Z_#ve?vN_WrYLo;1g9pV^G~>[_vNOS3 `0Q[ veO\k^8֔v<Zbz\Opbn$~}oz3ј mK vU]^iNWA#x딫jt q :E= z%օq)CcYEqyRG-+u (K\hP'*^ء^q=m=y|Kvūe\rȊ4={W1;=ݷxp;o@>ȘT\Ԏ+C=*ɫ|GJOCW]x1.ﵠ9_Eб Vq)v(ʑ}[GwǺ{-oSdו_˞׃2;iT&w*w:g׭SOsj%Z[~_˯d֮+w]7 `0]kIu+eL]ւoA^;=GR?v쯱;<y o$N1紈=:ߥPVu< <&3KyC/4r)i=*/|Ύ^]QNН1qGw>ù{ ?Kv:A}E:_n+{u=rq͓̳]>>d}+|L01`0 leg:׺񶊝`W,3O?]\9P~[kOWiGc~)-<w.3q}'vuw$Vnv(r52S;Wk_Kϔ8B/hEՠ'9w?K;x:x<|@cϽVyc@ۖSw8Bq]=2lBe6V}eR( VeZT4ade2ޒ+nYBTqSߔ<[&=f[|szP)G}{Zׅ3n7jpWwftEw[ǽ;`l? `0 `0 `{~i`oLy>uoi\qK|}7Svu9G쯿c¾#>,jow{ՆݲL=mW2u_8دjo?kD߱mw>#}E:OۡO;y`$j?tUmkBTxoUۃDbbbdhF^sΙKgvfn`PV@nBHQobL# R"`Fv?`.;ׅ5: !7q檎a+i߄Ł3'2j?/uG~ݵPZ~Ð??f/ NƴSO7kBOKڥ? ^AϹ8R pP_g 6k77?~zք3HbY/Q W??ՂǧPQ!&WSyQ^Z3ŠbgvP{bGS=G~R?aH[Gy)I kO??,x^>燛C13G,ڢh3ϝޡpJtIӢ:#+S>>>prpa&MGP&Gt_Q!@;Ѥ'^(;3)T0\RxTr_J5G\oq 4h:2 !ZYn ?,]A{b?? ?Q?! A=v~ M~TG' ?_h_&#V?%o]_??r}8ř??M?BK't'/?/짴2'[4!moK>D!!&~c ^O 7 3Ҍvt ('?/*GBx;Oߵϋ#ȏh5/c"bj? yQVfB}݆ی_G~!Ļ侣љ}I@Χ8{ y%^d(y/k^z!_O?cyQݏ m:i>]qϗ{Z.υoҞ4ou헦E=پETBmC{^_ n8ڋIv"3HBO/~7/K]?:{#3kif?o:~~/IPSX&Z ˹Q8K)B~|z\Ew z"Ew= 5:}P^@nz[LB1^T(?hrR^O'ŁT^ߎ^]>\Gד]S_ǯ);>y}vp^ϿTs5y);%Թ^O7}UC9W/y;yk?h7ltvPzOvҐ6W'˩Q'wNZ6Ks mkBT~x흍8 FSHI!)$FRHnw HYx3ꇤsaaaaxIǏ'U{o_ھgW9 o'GW {>~Jlo߾)*/N\ϱov[iZ_ձaJΝ/:6O- 92b?Tlk%?_21B sY5>:>c=1Ow y^- ڶ,XzusM#גU]>H_yYv!ۉ_mi Rus]Xm_g)YY)m]y,m z1aaaxEߓGקo/Y\k6xjgH|yu.\aæM&wk#ϐ$?]Mo\Ⱦ,/ڥQ@~6s?)}, l gX #vQg Bٙ^uのuhm?}{].~}v_J;xogJY]޳@.)oqC?}>@Xߘ'-(W? źvƔOʙRv[K?[A}?-wmՑ}g\=c}M ggg DŽ-B^k_g?F? v0||؎=ǧHPgs/hؑI t~{n^}ZyD5XWvO)"c0vY Z|~_%/,p\ɹyΰZ/;/xs_9?Pܯ5ݻ\[y|č8gʱL{? 0 0 _k3>z_\S |<)b|7aaaxn.ta?l^Cvkؽ#~e)3<3^kdlc&jK+o"e<.ʞ`^(3zu l+6v<ï k7]/lc[`On}򚄫 G뎱zt^v2)?;Wmr5ocIz?Ozx{&!ez."ѯ 1Gg{+ҏlw<=}GݽFƨ^)zIpG K֜{{e G12ۭqiumf>.}~a? 0 0 [u+7Svq֭y΅ ?ނ}XwŶv?ߩDZۓ-q/?߳=<~#>Fk"qzrQo 9r,nY[;o:)@-`ק-7({߯S@µK9֠ɸ>:n3 _[_*mtcmC>qSL=<6;ǫsaaa{xˌ\ފpx?0׋#5zяc]x^l򼠕(f:~٣^lin59W~\;?vn6erUbS~v^U O7O(|;+SG4|?f*?rW~2oNٟS9~daևmH6mX[J~s.ym4ٶO|Bd/b5ɿyU? 0 0 0 0 0 0 0.P~*1@G\⟿KrKXs2(ߥ纎J8'>X@▼QQbqwx b)_K|v 1M6kee-2Ǜ59?K^E~9ϱQﱮYF8N?~;:=J<-tĒyNAgC \NXKs)'^Kg\~2}6}Գ)n]Or^j~"{p29w6/.z-v:+M{WJYZ굢`% Ҥl9ힶկ#OUz+U?;sd~vND7*.Y+v:ye;8}~|+ÑޅN9}{Bƞ#txխsXɿkSV/uJ=o G<ջL'L:D]6jfgLz/+ؽ[{rCMYq~[{yy czA;w9zszWHVax3 ף mkBTxglE !ћ"!A;ߝsOqR-zQ~ $@=(HB$q07|3,}if޼yf3g0 0 0 0L8²+;ݿ={磀koo`\ ,;ݎwٱɸ2`A8D\ر%E2ߦ2g{&C~ضrO ;"yh[t._og&8 < WF"Q=8ŎJt~b`YfwJ0G6Ewߝ1 <_ ,>6q;{4Y'F-Rc9gX^P^1aMOE]q(Y"Wwo6ǁNmڷ lu]xgD_4w%:97S=gam_ &ٿJ7Km-=w9Cu xX2=Uvo /O%;RWN79f"gl5b<Bu?Wf_p}19;\]$] {k@$k|<==Bߢ3\ՓZXuWt۹R=,݇y꧄ٲv{}kNOX> 7n',κZzm~KZh˳R7R39|s߮>axZ꽣_y Nz.Ϭo[;ݾ+Ju_޿Z iy'ó\C`Cy~mc]ugxJduw,f.ͱؓ 1\Oys]/p}Ey'x7b~3f&z`7LvwP?9,6b{ɘ?нw&PcX3a%iڊ>lhy~2SG{{ʩvg|~d"Ԟ/ s}29F/uR*5q?h Y_3{SY׉.z}x PYYw*>{>Simtr-;@Lf-`Pe4w[un;vop?B5>x΋g=\rdz~7iGs?QϾR> s4{fөGi~o=xR:(}</oe/'Վ mkBT}x{l[wǛIIıg~?vM$MMҒ"&4!B! II h`e66lѮu]i4rνTet}ڑ~;o6im=o+-F|Bf.`Y(qWۚ1Fe$ĚGX*/߄~L^{ 28m?m}xxcpW;{_qߡU.ڕCkUȎvhIۖz56d%FK1iN($@cOhL.Nxz1&?I?}gO ڰ^Sse]ނ3<c_tGA7~ֿg"kJ_[}|w_I7ab>\ƾ>3̲})qaKCRRgCZ?{9^G=ȳt08}#='nINzl!>fGKsl(2/0ǽX'GU.+/9dSB߇50<'SZoq|/16?Sg c8 9c a~956b"k h-x ZFC-{C7 G/!|m˜)dtW x8C_?} kGcc{CoH.0_~ςA_4ҤnZ@7=ҿ8f,LW!"{UBFW!"{=h`p<2@W!w}+ _l?{SoBDvY>>ҿ YFu#5RBbH*Dd5>GozAՈnfJ?]0K;:33_ܴGl_ݼ@^19r1/VCݦ/wh֦H Fdo1_0 ?zC@vIXՂp!~Wk#γJ'3ۦ&+q71_;'hl 9nw-9cJܻa %7[ߕu&ҿBٿP\_[}xZn{ZlmO'XLBl~nvҿrk1h? ydzj:B_ͭ eCnXFlǦ'_3yׅqqbf|Aj ݩSr۸5\ ?@ xL؎,CNXFB Lt0e`b "5_~ja/h3WO.k4|߿ !/ޠ ]7 ~w;22;bFq~ 挤a~ny{lP[OwVt1;<2cE?2>l ^Xˎ~}<#jepc'C! >P~g.B㮎vҿ:Gk!O}`S0BH\":)6cR.d<:ۥV(1 _G׏|E`Uvd(sX[*|ǂ$-ۥV(vĽpΗ!~`">@RKPFfb~ 8)]ӱ:ۥV(I'=EDc@v/NLvh/m|hKfi.4/y e3#{qʔPU\k&u e4\{q crM}aϞ#!ewf=_foa;){pS>9rS?[kW#eoj+g#˶IV Ǿ=){f~NCL_iw5ߍdF;ڀ+iru$ OYo_!O (?ZpΗP?c߅~2h Mm NEcs|J w` 9R%ϏkBӱkpyfM-Ro9,Ǐ} 5x&:8"ĒqVXg6= hZw]VGץS1;3Н?3pz7c$kWʷ@*mjmCs#!cs c@wFsADŽAE'-a\0zO_D:c zp?i7K!^p6,!8sF\GKO ^*ɼ۠%`UO N;͌Ep xM w+.)F\{W_lb sOAw= ƍc'3-{f'                              /b< kmkBTЕx]lcG'Nd?'$N'7k'Nk'[nB/<ЗV!Kx+OH( BBHH(lZAhvbnGx{g|k'M2wnvc'oΙ3; 4MCFjREKi&/UTFjREKi&/UTFjREKi&/UTFjRE\|MϤtGo3 ~OK7Kqi 4{[nv {^0oprD`_$/UĥŮPv|bmC^(C>ޗ*¶>vp? ͦim 0dg?kޗ*v {$g64) O5 Ë3Aޗ*cI쓜6_V.Ua,^Kl+?mo{x>U{|5%rZrN?0WK}mIއ9ĕ%``I2?HKqI{ZZ }=9~/,kbX)؂RtzJz_Kڷz#ePӓOwu+:JIc qyi /RRE\;TB}a~'1N8ơc\o,"/Uh}V>q 7[;|RE$w~1s $ KLv_M۵c}toT-bns;ܫOSS+c[α$>8O4_a|lXyEXW>´1k:[J~п2{ۃɱ]\G b;>S}_azp"hx>e7ZfHr WBQ(5a,_>u.B_0fK9rXH_4-55^G˟_ />|k>$3uWkٟAgؖk>![ GOQ yj3}~ߴ+܆7Er +Nuz տQN׎fo,rq_e3}Q~}п+ />#~S޿ߴۇ/tt:يY|L-bOoVkZfLzx9߲c/޸?]0~gAܛ܋|os;~-m={\.wo\}A-}g>п+g}{cܳ=sZݛrP~??9Oߵ҂X[[[ːK>u%S`R|_ݗ{u~}/?׃''0{ xZO8qK|?W&h_.rVjr{Z<]/TGl0W7f{^Y[,[;@<~?6r>yf=]֫Iu_Rv_1x$|OzΣ{z [}|{K0=y)c1]T{7w7gf5a}"x$~?89GxżeO۱7{7[y}ξ^^_=C?sVdMy޼[ǽ!m^GgBN[`wX;x| <\,`;Q㳜Gpoyև7t4i]7=-,3,Mߨη}˼ 9l}r~MX='E}f߅/eF&̝濦g5};w~|??4Nv%32&lb6诌f>5[BTNwx1Ckg3Xvҍ矌l@{Z\ПHMv_"                         79?R*mkBTx}+(H,"H$"#X$,QԈZs>U{ ..T}6ڳ-F`p]k߅~b  О$wݓٱ|sCoA+q3lOx@(0a+? T,_7s\Ϙ^Bl1)C+k(FyN"8dPC_9>O0&l4Im+nwGrŰ)/tihf ѸX>E)<,6s45zb?J\<OM%O#(76:= ӋYAƒH Ls6MXBcX&ǘJte. 3.je(??Lj=%wZizFTx$kP8Em jAOހ>~؆B9 ֤8UKCvjbL Cy ;mj P. DkwUE€3ܨ8xUJs\ɟ+;}sFQ(KIXݛƨ 1 +KdX];Jģcx$D׷X`i @l̏rnm$^9΄zBGϞQ=nfkDe; <a>,⢞jk0B[p($Ǡp4 nq`XƓ vϵ.xHnorJ5Hu뇗 f a[Z:>36[g RL؍?( &w.7C#~B{] UW 71jk~ecGrD.=K@WDZM0倐0\xvqNZ ># BE )&yA}t?B Ym(WIpɱ |2+\2 )l8tl@Z.Be񅋍RSƃm>dIl'N adĢG3%#)?$s _5=YBR#-k"qGP-e"f%֩-ϓ378M9ϊ,_*n;HEBƱcl~ ˝[/sagIE2,z1t:kLș壋G){7ond{@rP>kwk׽ #kXfyEAB9uM4P=_lgW؇N#_nGpp ,ZUu6ȓVӰ0EK7*|]{75F\ԶzQz! uH>upT٣o3P)[^6` -d&*=%fY<^ط`_6|h3ء>2 Pq7ώ ,NsjF=B` 큳CiU)R鐏@LҮǧmb<2FHRqùFXi䎲OmGA}:*u f:@ʫRH.66jcGOpO- 6HKJU:Jǃv,3DZEƮqq7p?ȌK%ȧ$;?Qr6pP7`a^=R_)m>D3#£ _' Iɭu͋C-Rne㯄ssL<ȭ/R)|Lt_1Lk=rr 4/gEr~PnB[\g[{gYvRW' {Fem1{ wL;7&$xc0 n&u@5sCCձm8Heft x{q(aтa?Q%l4ςxmWI׆GC1kQ3iJh,KRO`ʲ4)%b6B8\pe;u)ko)#WSncRx{[sXv195_0Kՙ7>Tp5ٴl3S"؝LX睫[5m Q="u}pϘ*xbՉ#iM+@Z! Ϯ~jYݬ$?5mtu] %@݅:4h8ۃtu3; ΑO1A/r R*5i&j#Y2:$Z(ad@>'z L뇶6Z8|`6"X1_z' F-я?X^ A:?1;h/KVB' vOnFS ƤQ{=kh7MwXQp\v͓O/. N3HKRlK"q^Wh1wt h@3e6N|I;y?8t[[! $,ήLe"z%IކAkRl!3u8ځy?_W)AbCO!rza5Sn֗#<43y6"R߃CQ&>[# BHǽ{vekOTlq(UH͵h ݔ8,@tՂL{p/*L"d_y k,4 G̖bD>,.ok"D;|7[.DCA#ilϟI֬Dq]+eE _-- ڰc^Lq1~CCC9gNH8BkhJ#Z-`VoMa 9r$պZ-hkh ?C$ ^tď9d(8P݅]ڶw[wl;dn׆oKd Hބ(DInI M_(5)6H/Y1 QRk,nXHʉ?>df&6^EJmt{CCc`0ʅv5x<\9Yc}106"״!֏9dl:' 1H"z'7QqɌ#KR./CVgQȬ\ `?d1yuM6Ƶ8ZX]8^pwQE &1frRKi$GݜЕh3'{;;~FK37ku<pdʎ+C RMzƏ7)nҀ lEGyl:̑IoBS%|ЕsTulebA}Aʹ10A{KʘӺtjdLI=r PRg_LbR Şl?␔)![Fo wi&k^CV(t@pW2{hxHGRn͉eCbxԉ6GQd27\ثdS=\Ff*0ۣOP5(rZߙxQZ>~GAeN-jY7Ҿn;n?ӹ"Px}/NW:݊&׾:x" ꭥу;R펔 c䛅љElmG§a= h¨BG_uYnZ쫭FYs U"zM&:Gnu.DX5Xn;}ԫ%XO?~2&Frjj8 yA*W I9/ub)Zl: s 85J>~iI3Yԕ;:#hELם[ROd^GA˩f~Y!En0~/A Km>^WYq"<цF*c:xw|͞w%ehRgd9̕v3v Dgh>>?3hYDkgC(ʹƒԕSԜ| 2Q94(?OGQ34 fccPopTYaW(>@tX4`LGٞpɄaŰl\[9c26U M6f,'C4i?W~psϠ?kAKrŵk@I|>^xs?\`,D̒5W^w DMXf_8<%|8_왉pP1Wlm߃f?4:́_Ԕv M;k:p_sj؎qw]$F}y ,b'N=o0, ~M YR46+!}@~ujctCP.Y(x׎z?70WXFܣo3z0c8RGg0 TU򄽻w"/4֏CQ`[{Ocn]+{{ N!33+5]qpj' r9FDȬ)~: 9Gmx2-?sraG"yvUpa;Ră A\& ?#n 0eed~oq嶭!!DzP^H)>oȑ.ļԶ=Hy7S-M ?8ycߧq|#5"2Б lm#UeΤVbM͘jAc7Z ]> 4gb s 2WRsKg6 's8qzTT[R[w)I95xWj #!nN+zPڔ KgTE,?{^RDݥ=Ru^zîc&D'i74SJߔ&HUG[crͦ<׿~4}څh;lpAZ%XZ;tQ?yk1+Ƴu6[ Dc4Ɯ*dB#!}e>samhG3c^8u9󼵕⸈߂UyB;f "Yi=D =4&|C3g]~WgjhSIXU"1A5Fr4{AljwTt6</N \Rta| i>T.Wo>>xϯY{緷m,J{gg}v~)]s!?wXGFl!7U|Cnfﳅ:.@mq%臔Ru?.:aBֺE#Gg'yXDuSWNJD)21ѵVagWPqȒ s?¶@g")s\T{f3go^w:^"{d#!φt},nyWFKv„X4|VB~,˘_&fjp/WԍwaO H 3I`u1ͤ+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_W|+_Wݚw)mkBTxYlUqqc4&FohbTј_f3t 3], &.1qy=>AKFEj*;)`aZ>|>sν\ל0t~ֵN[rU9Uy"+ttm2?N⬕.5eJ.mtL(ّL&l8}>5&w=ED"T1XWoO׏&SԖD"Q1Mr-?{L5wU$h"rޤIL՟ؿ5w?r-=.- d*S5~m1Tw)ͲbW6Է{=;[5~Pq^z{^n zz[1e1?O{_/e' hޮ׷'&0f`q^R=LK"W֫}ޢٽ8߭JDMa-w˥'a9w쬐͗ 9^Ep}h; 4K.1EcΟ1;+|H63d4|x6c{ܟ|-{m~/cJow~'vVڲK3~#?8f,RlVoo[;[C[K2á s`#EɺP&yT'.¯D4,{A#Է֩kWKUZy:Okcky;mkYׇ5Xn81?+B5 Yȇ`G2qP谜٦o = UVhmΞ(Bדnݾ;ۻXև\P}HXیru" ?7i}αO-l6g5<]Сmwlq˽Q?;xF3O򑬒HnK, cSs*\Kŵ#g]!B? Y4N&)-Sޥ|l{rbfXk4w/2ߙ޵==?sՌxƴzVcynb:psh]7e|Ɗ0nUKRSXY氃({6{F{֍5|]oɄ3UHG|ecfc6ԍT1g ެϑv+g(Ne^~-Y2#W|,[7*}r\-ITa&i5!K!p̶2;(ړ3EQz,dKcP^}gw(&^ҏuȽWe4˃r\E~UkeP sYT xd:[\d({H: TO)GC_1J=, l u6ۧ5?J[#?قTTLe]Uu|\Whsխc^-w-Oծsh^mwgnS \jچ\~Uu\u%+O!OJau倞H~)%+w^PagiYv98|7ͥiw+Nl{xu)oh++Km얟ux]6?Ԝħa&y Kur(Skdy*犚VW֜9:w-}\m_Crza{X JoH+ˌܻG^ cZjG8%r5 :q>8w߷l$Ur/֏jnKE1׆M-ߗ =>jc&>ߟOR6ƜPgDQ|Hxfh}qފi'SPFߐÊxW vg<&\mjUj^ǘ*ާ8IW֧]}tԪXԴjjɲ}䣰Gu ?/Nv^z0Rm>m{_9ğg`8Bҿs!S_ w3H$vosBφş)vGvV7;Y?-t-O +BuVֶg}Qڍzg!?jgF=BcÅc7~'1!#ػ` jmkBT@x]Ylcgn2;58L&dT *"xQPժj H E:BӾ@Z%SdҦC3t"$9]qϵΑ>M&|wmIѡ Ot,;0{4;KdGWc'㧏ggWײ;Ndcw>v*;q_${UvwRZ {VXx@~v_CKw`r<̟GjjjnX{[J?es7XVsrs`O0Ǻ^?}[R@S:&zuP>N38x9(5Pz4ub@e@,@pQ"T[of e]@oj|5 t5;=4 5g8 BY>} rtnw^?lKW@u k:W x4 ZgAkJh4Ԁ4-u 񥮀Y =!րj9RtY@ ^c~?ZY'@? 0e%ֳ S7.gjlo}<@Ff+fAWҾē#Is kfğXOwݍ-h>@7XXLM+z555ϸã9 X >0=4{4@UC[:W`äV~<8^4`ZՀ|@8`yN074 =.o+̓hھ4u5`|ԽPPUC|z3rpzAЀkpfg{JC/ƙ2 C} C E'6&z!=R xnPR% ykpngk:$zA+ NI?*ELh*)d&T:P}^N TOp@UC< ^P~P),WGh.\lZ_N_kY4@;D`IbtYXV 3:^?ff z P5x310\/Z&:5sk]C{LA?p |^;Թ(4d |?LKw:ݮ]t @8 ^/ {~v}=Թ`%AuRпUVC\dA'Tg~l=`HB79Qhlo};N0s fY~@ @$BN{%@n. ACOP.UrEYJf&sGFҢc% p@9PЪx@sj#h}Zkh3Qh9P 2&N{Jwwfd,kAZ??\N ^ 9h`qw׀oW s ts\O:Cw$$z! ^+ͮ%,&3p`&xi>PT~[сQ{r =9з$qM:=>܄>K'\s@7+.EoߌgL8r=D(}n>`vZ`Ɂ< pMq"$3P)(/صD2f3[OlChr7'Tg{by`@nFj pܟ}DEw4yh%07#Rb=xx7rhkhx?<ԂZ zB|r5)p}!x8r cPŊ^OƧ}oz{p@k|TzZ5`ӱᛊ/̘ՃjA<0<#2z iAz`vg<724Nq 諠)+ ̵ #B@I5֮jXq]7a7{j&y9`Qx-Dxm>=ܗy;뛛qGFq% 'jy?qz`OT 0OkkʾolkZϝ'jk<O;gHZJ h#`MPyw4~!/_{ƚmb+}=N|@ Ͽ$KH(cӽn {63_0pgzh}A&yT4Ag﵍Zg0w"|ۀ>~///gz'6_xMx:-',8o*:Cᛠr"<}" ӄk&hx <>6`StA 0?)d$e[gJ\+Lo{9fH i˙ q<˹t_ʼ輕:N'c)c74A {qA @JmP4[ʋevL=qrfAt_t4pQ6F3<dM!FHce+1ksU;<<^&@܎6,j/A)9 N[pF^_yOޛT2jx2ԅ] 7KpA _P'_}<x.Xp„=އ蜈֮@*h%<ִSm0j.*/s@jΥcWtCL9%_.O{J~AL` ZZhp =?lq~ P;u6N͵c k ./^uc! >a ނ?ݬ癞`^w$ ԓf&<N\N\N|--տe|'wso@~AKcybc@z;dY@ @ @ @ @ @ @ @ @ @ @ @ @ |7 6 mkBTNx]Ml[YMҴI'Mv؎8vv8I۴lfφ4HAFBH#1X TX "AFSJTSFfСœs߻N\O^|{yϻ7we'/C~HH_K~;yc,pП ttS'7TFcBir1q 9(B.v|s3Lfp|ll/:@On,V/C+KsKbyzY2 H|@6e6CBoT|ߗ7yTǓ'7#:Țp>q5CfĒJ6@ڒɹ_AH8}~pwb9ziUƍ A##|lh٨E&u  _@&1 nA&>־rlkqs<+XX4gDdcCF\Q+̙ c? wfEva:tw_򜕗!(Mِu^ȱĨ1Dd`GO P'~<ʞpo :/ Ʒ(z6*’ g&K)w!oA_"7k8/Xx`HlTjOjk0 mQP\[&Y9N٨EZb͂4f O>o^l梙LyYs Ԃ'YyELٰELY9S-% ,|JS䣺^Ʉ̂ @m+ɂ},@au᫸'^4㘫eaMםA?9)̗Q>`>gι: ?ʦo]hAZLϖcz6>8uoaP !'cBM-8icO/874Rd`tz=sd-hzo G#Ӯ_"ySR D Zd~&Q/El1Ԃ]|C X G3xp _M0tnhZP=49 } zZ0aOyٍwīK\y1}̤q}P:зp?h39'#φ$ -{CqO6b>Pވ<d}of}΁Qkɑm«hxzqۘk{@o7֒#ۄ]]~AP9s={VK+< չ ZK$+xϿ0wBsDڝkB>wIWFA[* :PwЛޅz@j-9QC&(2 Ƃ;a;J%G}\@@SAN~ FLJQkɑhx81?+z-?|߿>t|I3o"%b{0Y fRCwz# C@*2po/{gñaOS֒#UH@%Gj-9R%Pkɑ*ZKT ZrJ֒#UH@%Gj-9R%Pkɑ*ZKT ZrJ֒#UH@%Gj-9R%Pkɑ*ZKT ZrJ֒#UH@%Gj-9R%Pkɑ*ZKT ZrJ֒#UH@%Gj-9R%Pkɑ*ZKT ZrJ֒#UH@%Gj-9R%Pkɑ*ZKT ZrJ֒#UH@%Gj-9R%Pkɑ*ZKT ZrJ֒#UH@%Gj-9R%Pkɑ*ZKT ZrJ֒#UH@%Gj-9R%PkɐAAdH2#iTXtXML:com.adobe.xmp Adobe Fireworks CS5 11.0.0.484 Macintosh 2011-11-29T19:48:34Z 2011-11-29T20:30:17Z image/png rz IDATxs\Y~=;Xd-fVJݒg$Kv[8Ǟ ;Gz c͌Ѵ4jUbr{.73+L&X,;;(5B!8Z^@!O!@!8$B! IB#H!@!8$B! IB#H!@!8$B! IB#H!@!8$B! IB#H!@!8$B! IB#H!@!8$B! IB#H!@!8$B! IB#H!@!8$B! IB#H!@!8$B! PJ>RJ4px\N@ B@`lo~ ~/.P{ 6(㏃]}r?!F`v3u" |\2@ 0* G/GUg / ீn$s[M 5?rDUVzT B!zEwx@"L/ /BNݟ|_bnˁM>~?!Q7;tD"qԩS˯>L&ccc_iB!{z~orr|{Ӈ*!P%Z_?Z/i?ܖ|tz _Ko/޾z5o|gF+[ޓ;LV)w?cyn?ܖ|t0 $Jŋm۹]B#h~9==}Mk{i!5O?^}뇘r`斾sD?NQB=Df?O>ӾN?ܖ4!Z/~t.;cBqtJ/ţrZZn5 ?kv 臘r _ D l`^?{;B!b/ov֭Vi vJ!Q]~5D%$ !o~?*6RDR'{7 GׯceYw-V>!266v?ђeY8]vO{:g%_Os4˲F?!h?M1?O>i}l?ܖG)73t?TB ۶sҹ n󎚾ӗ @sO)) !G6,c[iN%]nl=~՟IBѯ._zg%~ kG}4Ko)MlSB>gO٨䉚/t`@)"IRT?A_OB~?lVF_%OtŋB\ŋb }o.QĀh}~L!xACF$0DٿlBqض@TO=V~J!Zo_#!զfUG٢/! h__!Aevndx7QOoEdX끷^B!DkƲVmuOnD%8K .yg!}nS,3vpKa HpBQwz7 |S =Bk1s/%x$.z_ߡ,BcBcSL3^S$Y:O!a)D1%h/Bv^'!B!ġK/Bbu/ !gOK B}$ BqI BA!G$B!$ BqI BA!G$B!$ Bq%x)\ &&&xa60@͟S1Lt6K*qԊxzJ&?˗/z=l!^$(ܺu9* gHHGqd/vRL&I&Ǩ*k4AMϟ7 HIWVdzzBAH L+M*?L:7B̶{=Tq cgqrԊ4j4nTn#.]˗e{8LNNy4PfTvt^_Dx;FBFoXYP(055o͹s'B~I^&ڂ `nn>^ JH #{Dol'pFcja ~)>䭷رc\-K$BP(y&=Zm|ʰgHƤ_N`SY}p +++={Wf]7$}Vq}߿OPyx>Γ'L>ѧSVԋx2Ej,--qe^uY}ASA#޽z>Z y Sʧ)dr$)uW#7J>ڵk?~G$g wavvJ ÎLI6 g}}+Wp9DH IĞ+|<~ZQ_"G*t&/~qd8cgs x2`y zB[o%w =' 3233ݻwY]]B Vdvt~X,a Rdr)*EիWyz=TqI D^͛P*Uh%Ҥϐdz=D!z.f S+-Kg}o6TC$b-//_8!`%HdF Dz?;!ZaœTp+KX*1>>NTի)bWobkӌzXYRI(9]mٶбs9jk4*A,r~_$bWuk?@cI_]yI6XYY嫯u]]P4I V|6U(+E2;Jn踔M%uz B`mmor̻K2H# x!kkkܸq9j:V4dD?!10zrajjY7yCH&N?vXz@u?~NTD%9Ȏ83ƅ8l;An4Уᔨ>|H{ zǤpĭW_@ A Btvi VN3x1]LC!fȍNEZgaa}V8$8J_}+++jڈώ>iʐI̶ NjY~KpIpUU~_(۩r'b^ObMn4Z8_5J= SV/5!4,(srZ\,#7z;-j:|8}& h4qN/'7$3! ˲ȍ]$! FRavv7nHpHpDJ_[ةaF% qضMfV2ufff}6zxbHpA۷ b/edNcAXݻܹs z=<$8&''\.`%dN BqTD?ݻh{=<$80dbb"^fcaa0$(z=BccYPSV$<_}R /PvZ !^Z"$3t KᇚB!롉]" !P.}6kkkxAa$re╤y`&p|gr\!! nbqq##;0 !LnxfP[]Enݺ%I! ޽{LMMQ Q3#dv?!Į0LV"K$333xE`KKKSV BegJǿbwYEn B2weuuC@\.sMe&H ǖ?!H$S$Oh8N&^$PkaaF3N YNe֗Ve܆ & ZGǼj$:(e`&Pf ˴Y6JefÎ' \"|㺄"!XXeff(UI&2Iib,nBy Jmvo΀77xqiuGB7 zqɁ֐2 ~'[\-YF9q,cދiK8Fp Ξ=롉gϕJ%n߾MVCD(Jm +sĬTò *U j`[PAv7Jm[f[#~+wictf27>x,B4WSHNMʏrFNIo> 'hr۷o344D&SHǂ `bb봿Ib1)4''K$ djSrP]#w7ަ";wu<~㍧^5΄@gF}lUM 45JխDJ L?y [T1N8-زcf/0z.KKKܹs_v1IǏ1HdHg =3ܟғǤibQ:P 27{ǟ[f~u]5]l"Uؘ嫍ډBgGW56%IA4,-h:1&a'ey9v̮}G]:ǫWpΜ9É'z=4IT^g||JJ{=, *_p~˴SX1RA:7wVh=uͿև`vϻew@kaGgk:՜oD[\+vAiLDS/~ęI: ){UJ ofppDBc$}HkFDfLS1.MN&;eƨKTJo6gQY_O fk+\1~Emm^{+՞7Pw'݇z}>ΦAU @FX,D2;F}mY\\؆$}huu.VAy/sKbVxG|PE{t%n)r'%0ڍ|J)hSަxu+ҫMޖ/y}㡍n%)Q[>Y?B7%u Y `$SYA055ӧ9uT&:HG\ݻTUDL^riw>g]r4 P2km*g@ƾYN]:g[6~׉}u|6i<*̈́ZejA΄{VQ#b4 Q&) ߩ%PhԊ^ZƝ;wŶ$~! @eqqFG̨C^o~[d2y }7fj1`S29;;)Mqtv<Riuς4ÀJDRL߿IQ5qjq,oTlz+kxT덟[g*̀J(xvvxfںK᳴,z&$ZwRdtNzem>ȤZsSf;m [f:)wv7?$ORGݭ&|81&cVtQcc5TtNئ1+F6 3!PI@Yn۵DVèB4(U{FAnp^w .ɓӲ$Zk7.1rڿYZ_5LfX/=.FW\oZAOm:iZc;UPc*FO IxנPxF;l\/Jt3i ]oH: Ziձt&0aj+&aS^ VWW[V&/1==M t:R_ dK()oM}qv3Rۖ[D~#wf(7J+Xɱ=E&ۮb1ѓl?hD`#وFŖa(1< ߿{y>|V$#$j}QUկ-s d9J+] ~Հ-tk9,7ɦ򠣋N]׉٭w#\k|,wgAiʎRBK6&PJVρxjmOC$)A;w^HǩV:ĈH~XZxȧ_CEÅj;o:ʋ7x*o$Ðl*lSq[?v'Nsn;a`vRu?Qg.lhFStIpw{[) ůJIby>A`ʏy{, L6ja{ov$'mym ϯaH:T&h͛ͩDz~;@\2jc@ [?gVZ_TI+~gy%)!"3;;˕+WN" @ݻwZFCL;EZf{^˿(WWw&@2c8nm7=JA&F8TUx{=#Ku>| !C<FݭS:eib&bFf5h%{!it̅S}0a:{>:z|VM^Dq bӃ|?`ffraY@kRHuhݻ5OLjv/풿2fEu<#oxۦ{,@fo}HQ>.Lw\zD,m  J^* ؒE4RÌ˾]VUpu,o$it7w(OawOXqrA~7lv`~$R[ÀT[@CIz%LK è 099I֑" >X,Xth8?@%hK_goۯo kMO-hmPMZ}eY+4>ҷ+oD[[5V3.Fe#Uey~>j SY* INԦfjcO̍ ? T* __<շzaD|l: hS"A+dveXT*!j졩) g˭/ej+sjfp\(Wgce87J:w_;#ٷHS[; 7C<ϓ ${VHdMf{Odz>l*PndR iaZd `D 5V"xh IDATe ':gZ߫*s!ʥ?BO%VR%2U}EEa֊x2!f7?;tkE;,x^DaP*^] +Z__hh I<)20ַߚo}l[sD22.˽ҁ' +ZXX4eUiMlW?SR׬b::peeC:$xE0 1m9g͞ʲto}p $8")DITjH\C׭7|#_~r_ptvWuweWuUVVfTITj8a<gj \ 8`Ɨ͟>A>__ ԟzJ)u>}nDO^0\2PRb}}=k < gUan /M;q*SQ !@4 ngmX7o}*aHS <ÿ_n7Cq Cmi'` PJqt:Jy/$&RT8퍔[9{ gP7x`T6;3{'^l[cs)8r9Q#4Mk)?9MO"PBh< mwp-LTjhK< ^VD   @looDN/?ZD.~Ϟk$-_L0b$IP =^G {=k~eߨŮC59b_L( xK{%C]EUЩ!K)B1^Y!D҇;;;$ y} ,^Hv7FT?-'tT Z{!DP#O i`7H@cz xCOsr;cD4wEA²9Ыx8\QKО4[`"FV4ƩcQJEz2z̎NiS.G>BE`G-P",źZ"{G\3(6vIF>BqU |ؚ ˲!c礣amh\殳7я SKQ#=qq|4-y7tD!%<}=|gOcriQ*VāHᛸ"88dPJdmґ샸T*Q1О<8L"jLBR  q'GwqDz F5G = %M]REZI$ U3}jP)MPmO,z>MR=/BjTЛ4VH)~(m$ $y}n;q==W^4R mhjqG8`.J t[6@z\ _f=NJÐ2UͰEn]>HH Ʊ-sȶ`/Ti8య͜'ƵxW# }BS=0B~zȅ|8 H +]Jf }B`'OQ`0;?_Ǎz.NC H`{%G eC1 )!FHSKKF)A:~O&^SȸpguOǁ7(@Α%$8N>Rzv<-@~ׅڭDlu-H9<*I̟^l=w8 M229A NA _y^ǟ qq]^#p}WP>%0ʅ2 kH(8wGߘп@ڸJoJ‘ P{LiCq%e8,+@٘ ӽO0J,),5q`Q(R{Ɨ~0Addr08"()%@Cl8qQq \pMB \g0a.&gX/q9?n0} ?(@ @K?1{ (|4P* v' F }9$0yԴ>vgIg~,|M uT2*x 3;O:2(_Yi) A0{ C`m@*p"Ah62R#(=k@@kurxh\SO9t: P;H*u:b)yS$4;> FԈ=7fpBAHom­D6_WD>B@A0kzP ݺ w 9M'QG@ZXhw ueE|MH샴ڤ (=g :Nkd 㼻e0+YIKcu=ck&i< 7CXUF!qV tjkV =gE">(6@nƌ)%GT ϾUws9-b\^6i$BE`$AXUS@)x1U1)p񼮰 H"zP8ߓ׃>0 # VPE҂ K n2@ۤn'/pPįîLee'=\.ghхj FE2V{*^P!(c{H ߷GkJ<|+؎!Аr=7Jv(cmp& Kq-={(I>RI=T::c J8P*q-ʏ2yYvG`ot&&`T( &COCT S/$ 9XE@P " }P,ѓJOiA&+SѬT]_wo:XvB`z&:^o ,xãg|.ry=55)031whI, 8Ec }i4M`X08G]Ɲ/h6P1hvFkR` . ull N {(ԩ3|w=VJyzf&a߈#JDַ{^w?{=~?65 ;KX(uΞY=EscZ  2p2foѴuXt)ΰY}c!'rp <)ѻ>"?YvkZepz,*Ip!`vZX_Oc%'' ̰ }kS)<ˡ9&/SZp?DHi0Ơ | ,N"zUa{vԟD ,`WuDoq$BA`|󣳇x { pb;cPJ'|+4Z[ (&a|+B +nw( d&YcR GiA;dw:P*;| ^N<8twUʉ||yt d)t [a-J( 4xO0<L q nx9'B -G)IcLdz a􅀃;B=l76FkB}Q-Mj6 (3R-&τEo1Y#^ [1*< $={|Zde $*BA0:t! GPsLM= 9 KPRbc t 8N ryQh3*zL10=ׁi!K:fae+Y><  p)|`P: |׸sf<|'5'70'3xA!`^/oI1s;Ssglc{9M; خu!eqjG2 -{ɽY<7טA pN?r&gWOT *rkR`S/ U[Gdq0`*0J![)p0pcPRq( 0r )% ؍d5 $6h[͑E`\ۄb qy8 $a(ol6i,abJVUI}m]l`xA@<vFjaڜeexbP_C\乜0 "<uuPH1:̹h",VQH<<끸Ȑ@ cY6lKx J)m윟 9}tR(}gmX9ǩTKPJ&폝WD_0|yǵ bB84MC@>a!mDzJF_Ʃ :aN&0e![BċvX  ڤ# "@Rg)xf#kƖVjQЍn!KMC=i J xR0GhRL`bb"YLpaccJؓٳ vO@80 X4'n*b@ B!W@ :fs7w+rz4XNy5Q\"<B,,,dmұ!033]Tö-JKN`R -X{EeC'i DΡcy.r9%W>7x>Zf#)z{o ײ6XmH &''6X@IC ϣZBDsdx!BZ* 3b]H"] Fgi 1D,;@j,p!.9806!>xNf\(@0 :À!éSan3i|8h\T_2wNM }l ˽ooarb8<`"cP(6ģYpӧ6@XXXҵh 0J̟: -4H&D,:'afgQXB2~/?>{[SGy.T`Cp9 C!177] eC/Ƶ܆Ikਢ z1P2{pl ӇOa؟NL$B 4*jA8">DHJ¥ \2PIJibl}8J|qw0JT? sӏM S[`ָf`?V ::39~94MG@ Kǂ]!pj5!8T-x1yG+l LZϺH"gT/bqxDjh +î L;u((P.QV6XAB`nn.l/cz4gFQ`b$ 0<u|#$l D})19I;糢܁RaP pE"'UM9\}pUpح25# @wb P AAl e|sý[ ܽ S;p6@0DlDŽ{'6瘟!@`1MӁ{Y4T5\>z.;zo@0U"zݖ؁n> _b=N뾂n@Oy.g&gmұ8}4 ʃEiW7gSY8(DXis҃Q,o>:'MA|?z?< yZfk2 8aT0 0u\Cj03`ةQZα|·lQJᣏQZtvOM(%s $J ;mxIGSs83ޣ-pW*`X]/?Ur{¢xvÏf?K<=L"g09J&[H Mp(߇eֳ6p{.&+SP dO4`W* ͨ,>fł/T@VؖKTʓ?aDB* <&f2x?u!0$"N> Spۻ1Tv ERЛ †]Q?a_y]c<~txe{0ư ᐟdG' 0=MkgG $s &p"$r#pM8xUN/,a(aA>;0i@}3ܹ g|=O^2 \"6!] 4C9}424AVk;k 1O0] #i -(WT1"ǿQ-Oށ?]_v_+bE8Av{ P4arr33&$8sL2\lmWݛFXNukS,Lj o774b/>o f}=-LGq2y T8j|߇k6XXoW% !syr9h<fIGS x{>PRBF/ {>]ހh ӻxDCeC{C<RpPQcP3 ;w.k=$4j```6i&krE,-(/ 0!Arv|^fuO?%ʕ]x_:Wt <͞! 901A !9ŋi8| 66H8\SY@BD{R]-(zw.8vI蟋QrMLNRyؖmC]pyZ;HT*>V&I>xSՙ$ +"гFCS9&>} il٩m qIDsil@gT*X\\ڤ 籴PtvAf9*~Nskp" /Se2 nn |_"Iq-G _ i=?(Q1px %p#buu5 jdmґ65~y.|)%q/J*It\pַO? H "$/R U 9cfrni@IbzG Q077\N!ނ dfIN/\FE@zH{ML}*X~/n`}B|_ˣcݼ?^^3t@BO]̟Zc;@mm@)@8Μ9JY'#ŋaK!6,*7K˗qp32Hd2'Wndxع$eA _dWf|3Ssjnצ͍@*,d Ger9|>˗/  277)!e,k4.k?@{0r5S}Jj; S҇ZinվIonΟ Y0&T@c;H`670==MF :Ο?Mm+k4.K+PC%3U8fgA8ؓpY?02Cr*w(㖿DF'S#~'4\.,#F J]a6gmґgm\^H#_%IAE@!5KhNEɨDa:{颿ĿT|&Bs4h(PGNpj5={6kN$Faɐ 58s\|7> R-B@9&9sxAcÝ᷿۟$8¿~>`,Q`[&<4MÅ `Ff8HdŋQT–@l< WεD5Ta`+d_:!]" U?V7Q,/>' .>?㳯~ hڌ D7kOw#cEBA7a V9G[/Hd@\4Mkq( p,-_71*3PAZȞVdhРECہ/}<|Od]ev/-vݎZE߼v^ 5'}PTu墁80e! i+++tZjn2~ u0]=Hj[ K v4_E@00:uM@/9wݽ N2Ghb?YE"+gֆ0O_>?⯰!=+Gcg_/$6{~ڟ( v1[ac&\s\X,fm։@\t)Ns#kF?Zub $лQq(р~0[thv~?wZ+y.>7_\߂hndϢ7j⿸Z(FO4JJ HAj/ !!8{l6\&z \^Q̤T$ k#^V t@ nmB7_Y-wn_qV9)SnG\WWrH|RЄSC'۶Y;\"glѲ6s%B IX諡JK0T#* #= C8cT ˿q\tՉ1<ܿ tFg'A"gm~<:oK;AOYdH1q0GI (5LNNbu!1Z ϟg}p۰(/6&&7ql%:xpXI ߣ"ݟ%!0PDb"1Fbb J1(,BPvso0=q k©1_[uܹ? !4!c,j!zF'ߝ?ȀTg_)R J%Z!':gC!ǒC=$ƀrU|}@84?[iP2hYW ubcՅ@E@*%+3$TЊ<~݆`X:{ ssg冻F)x{4[;T'!DޖnL Cmb* Vc ]/… (i8@` `x!={`5hTi'>?~[TI*đ  zDN 0$B1R\t:-(P.V6E0eLVg03u ˘=PuGc4Z064MGX N8Zo'{ r{MK>K?in?0YHh`7! -#Ȼ b׮]CDwZh7095iǞe/,/ߡVAˬѫ$5a0?ߧ@'/ eE~NHHN8 lOރhmCrBCNQ5A@An.<BJbb!4h9 5xv⼹=zI(?v}{}rOg/F,OݡOI+_B 4{mCP+WP( 0F9swۅ܄[gmډ`u:VWONӕ4u\ݏ“bK=sq92x!:9d(B<hM oR`B@<2+^9f =؞ wzr,*Zr{\YĢ-3vq;9l, X-Y\ V9 Ν;G1尶uxk sK{8ݷ7:o?}DuvA!\@z,d@m@`qJ@D?>ղ:xtG7U4 'pv^ nt">Op}ڱ;ğ8w#xozf>2'fS0PJlCtRڵkYEA`̘2nݺ_:货P#(U[kO´:&am8Ʃ1ƒިZ@"ŸHo`q/{} 1j05+ X!'W#R?9'쎛$?kKOt)o80v n{)y`zz:k>HB\|O<wa֟`By'>xh5ݗhw,OtvX#o$ ]\  ɭD@18= Hеk^י8oz K ti%4R NT"Q= |?Y Hz.,rJf 0LNN7G}?.obb R[ozpO7>C)_F^/e69 Pq[**@R~)H`"At]g H O }v,Hikw^9>F"+iߛ]/*s8wQFJ)v[ar J%ڵ0SVWW#ܿio)O"OsM^gOpeo- Xhv@oT@@e::ܥ E7?/Oe@x߸Gd"~R"[3vmJ tOm9dT8 VQ:G2ᴷAucii)k= 0!o`kk _hW k]N ǘvc gB+++X\\, pƍx)\ρ\G(#YGSqTMd>m@!w.b1G ^-O"CN?<{+KŮgi,^BL5.Gq`7!=e?ׯ_%GGj5m xn ǘ:\%2+X\\:Slcc 6*F A)@0vO G/d^قWj@?״0g/@|k)QN"&&i]WL Ht"p;8* z-4^@q9lnno4mxmT'g?Kdc Fe,-u))aYlmckgNVC0$tM C5MOy,/%V)t+%B]( 8 @Q7P)MbzfOPqA)U߀WSA:nܸM<}n>CP"}`(U`*8{vue4h44ѱZP <@)pơ|"W)H%*Sy819xt9ZB B $j(>)D&fvkLussszjDIDATf #ay&::\D{1rs4pÅ@22͉ Iφ.8&'z*UAr\xa@݅XAH2jBc rw̙M# #ʕ+WB>8-;4Q Tt6PбvT@R,quLOOC׆U66 cia֟Bz.t-lvr988͛VDg)|4 ݼ|abb׮]՛eHacXZZ… P* XFc1d 6 cR 4.Q.qeZ{ p x7pY 05jlRQ ABI:& P,7x1y/x 0 7n tM|4 0fV9t阝11Vw uY0O`V֦q-f ׁ LOpM#H#j +xv ͍(X Wu\!pix뭷i!BUKNͭ'@+>ó8P.e?f8f躎k׮a~~>* :Ѥ#bDs<.Мc cH>~3 .s4wCJRAF)(bx뭷P(6$)Jo&P2 hv6 1CIV}vQCӦc ct$jѸ` v :v֦1FuXPF>az7nVem1DHccXYYoZ|Am7iemAYv kqWpu?8&&& 8 G4# N8fCN `“*UHX[[ŋQ.1 nE $b4QT .dm1"Hr|M\t j%N͇p'k!AkPTʸx" E hLdXYY?''\.{I$3hm=G8o[*@T n޼ 06'b~( v6Qx@8f8fx/X^^;CSN0$N0R >VVVP. xF}8$X`Y''Xy:pHp* n޼3gΠX(@CzDz6 `[&ڛÜ?$ VWW?-k왘CǏnwභ](3 BC~#xv 0E;wrDEaM|/R)CBg1 "#FD{\ ˤ?JRcER7|h;:>"*IآB]m#'8*._WBM$Dq$0 ܽ{z׆` J 4" f}v|D>uׯ_>b$]躎WB)۷ojl>@໨L@pJ $Z簚Ϡ| GVåKpuK 1Bw}R n\߂w01!CY>Z;4ׁA^ג~.\ O /ҥKB֭[܄p>*SM$8hn>g5Pcz7|+++YG9$"KPTcccmo)02Ĉ-͇ `,,,ڵkXXX<@m+1??>T©hYG'%%-46/){`TK$WԩSя~K.Z ׷A&ıF;N;=Ծrg(,Y-#Fz@\$2\&IA Me Ei'AN=;e2eJ)"ŭ_.NU*@Y*$s~14nxMrk5XKCDSD*.\q>c&a۽QHytZۍD0hm70I@.P9}4gΜM})\xj!twՉc:jTi06*²bcc#?'N`̕' Jݻw|ݦTjc؎~0|[Ilh։SSSh_5lfnnr̻˝;wv~*Qإ66C&o3'ֱVꫯR]r,(?X^^fgg?tvI. ~)r( wwM," z_~3gh< Tr9^yx>twhޏST:L+GnCK2JE&''9{,Ǐא<5 8"}acQTx9wz% O]^7؟m>^>ס<hI}׼Ov1r9*gΜѐ<3 LM LLLpuݻz. ;Fet% Cs;$qD.cQ,ٳi_yfannzεk׸uN0 DAbA6 P^s8ML!Vٳg).S8y*:\vצԩP, 9Lbp!Y "333?F2eH(sa6 LOOp֭ڀn7unIaڹO$|ېg҅~ Ţ󣟴\e333{.g)JN#! .a;Iel2Fg2==-)sg6LLL %""ovw(LQAk2=21cY YFGGY\\dqqOo<.\`vv?;w.~ֈ&^e^&r\.CT^ŋLr Wmh4,//DQOP7(Uj- -Z ŶrB!OZ"A߅2P,9vΝc||ߥ| F8ܸqu B:[26{ sgvp#2dsBa$~*d`eYN8Aܼy-|':$W82MPԈ Z8E.kYٯ/JeQTpNbeevww Ð -Z.$ٜ._=x-2m9dY:,,,ˡ ^RϩSXXX`cc7oJ%I,(&tm:vlibu\@x.^aIX88T*?#M@,qh4LOOj}6| DQDDIߺڠɑQS(GE|"twCcl"g?R8q T~-)ȑgYZK/q]n޼$ Q.*=||eRy\>?E0qBp--ɕG)8$N.^wk1`XضCֱfTUfggg||\䕡"(?x`ee>NC&1bc. dJ#J5$1c aumK`f;v_|q6Dmiy׸>n޽{K ∰E٦8XNLLX#_*<@x^MwH"$`c~L&C\fnn'N099/%DD6enn90d{{O?wl6 CBaAz:Rl_#I AmϏ01 džcdll²sss;vI/o@@fLMM155E$t:X]]ecc耕>bbt6iac96S S(Wdy!={$ A{BEwcD{ ml355ERQyB "Oȶm*jSNq}<(H!N q- 6`L'_&[d2GPyw6QG&HL:JXdr6v۶MP`ttYD6ݻwY[[ckk u{ cb$& Bmegq9L'[fd4 (w#8H$HI_nY؀eOdYP*effFAVSyD2qcllsv;;;@:m $a03V:macY8l,;q dlrl ˶Ҁ &1$&cLc80q"$:&$ 2  ۱=zJ)L&CXV1>>4:QYSyJRcǎ?>۬.nwtB c1`0Ҡ$ {`*8X3XXܺqeFFFcrrzN^'-HyF(bwwMhZ 8~8wn4r˲0|ae6doeEKlm2 |B@RV7rL.yDL&DEvi۴#oQ$~`0%Il;HGq"bB@TZR.9dDL&CRR/<yR׿DDDD! ""2DDDRB """CH@DDd) !!4(ݖLDD7(%9ɳ7( b@3 ""G!y}7(``}}>"""< W- JǁuD:i1ѯZp>"""ic9%?Ν;#}EDDyJj9hP{@:~刈<zvr J E:4bZwt =4(CF@eO"\YZJN:4bln(f_  h>QCC/X#"\4[on_+0CCG Rx +++4 ""M͕ jN:=&\YZ $ S(6oƘ:?~ӟ̱z>&% fs M:fcW?x,-y;S曫}-LDD曫]R5PIJ6iZwoY"""_WVz̀w^B %nxڵ7t@T]v z=/vztk`DoW~i4?>TO,-4# Ƙ/^5("""/^5OIC@˲K/#`+ĚzU)""~qhQ?ŕG.X=W6IG0QtްE{O-3ofG\| 0XΝ{Go< EDD;[K:A{EtI?&HCPVZ6,l6\}VI5P| \|y{{<o߻jϦRFA4zwWVV.Az+sp:wH 80% ""O˗4}>ɵ+KK_{A{]trk?&销CoJ`qb|W;w.3'?_>сo goA?spe6@k/7,-5=wϡ {.]N"ùOóv>ùNÁÀâÆOßÀâ∫”ÈÅ^Á.:{†øáá√·t:ú...ßC∏.ó{€€fiˆ∂∑ΩÌm{.J{q…J.mÄ^ÿ.G•^j.É∫."µ.•È.Nı®AÉyÅ1.*à.íä.˝çZÛh@.˝Ó9 HEX: 789CED5A6D7ADB360C76BBAD4ABB9699527AE05A526C33ED04B9937FF73ABECC0EB0C777D90DD611003F404A7D1E2A71F24BB4624AB61C10C08B1720A97FFEFBFBDFC3B7C3B7EFD82EE1F5FD72B9D0D985FE2E9773785DCEE7339D9DE9EF7C3E9D4F97F0 mkBF chunklen 72 ignored: ASCII: ˙fi ˛.................................................................... HEX: FADECAFE0000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 mkTS chunklen 11488 ignored: ASCII: xúÌ}[w.7≤.˜Ã‰"€qíôΩf.Œã◊:ΨÛ.Ón.}{.EQ“X..I%V^ºx組ÿŸæe|¥¯flOU.h6¿&.†$JN⁄N‹d_–Ë.Ö.U..Õìß≠.WùfifiÂU8Ô˝ÎÙÚ*äF.>.«Ûg«{≥´`~!7?.µgWQ»ÊáG.˘°∑flü]%∞Ìü_¬.≠.(cFÊ›„„.W≠.¸≥∑;x{’ÿiå.„∆ÛF∑1mºÇOóçóÛ£”.8Ú.éºÇ#a„;8zŸ¯7úÒ HEX: 789CED7D5B771B37B22EF7CCE422DB719299BD661ECE8BD73AEBACF314EE6E007D7B144551D258170E4925565EBC788DB5E3D8D9BE657CB4F8DF4F55016836C0261BA0244A4EDA4EDC645FD0E80F850F551F0ACD93A7AD0F579DDEDEE55538EFFDEBF4F2 mkBS chunklen 190 ignored: ASCII: xú]NÀ.Ç0.ÏÕfl..É¿Q ´a´.j.ohl¬Uì&f≥ˇnÀ√Ésô…ÃŒfdù.,.>¢O.pç.ı3]E¶1.˜T.•—è.jÚV£„ˆ2⁄É¥¥-ΩĆ.ÉpË.¶uy∑Ò•.Gpîod..l`.ˆd/∂e>…v.9ÿ.œ]ì◊Œ»A..≤¸.›©ò6ÕÙ∑‹ŸŸûGï.√ÉT{.1ˆH.˚QâlƒH':äi—Ééû´fi≈èp’a.'Dô:.§._∫._¶ HEX: 789C5D4ECB0E823010ECCDDFF0130083C051CAAB61AB066A046F686CC255932666B3FF6ECBC3837399C9CCCE66649D1A2C1A3EA24F1D708D1EF5335D45A63108F75409A5D18F026AF256A3E3F632DA83B4B42DBD80A0078370E80DA67579B7F1A5014770 mkBT chunklen 1145 ignored: ASCII: ˙Œ ˛.8≈....................................................................xúÌöâmÎ0..]H.I!)$ç§ê.íFRà?6¯c◊ªgI@œœ˛XØjjgªi...–Éûóó¡`0...É¡`0...É¡‡?¸˙ıÎÂÁœü|‚:˜™Á‚s•¨.e¯˜¡ˇQÈ˘ä˛3|ˇ˛˝Â”ßO|‚:˜2|˘ÚÂflœ.æ}˚ HEX: FACECAFE007F9F780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000789CED9D8D911C290C851D881371200EC489381007E2441C mkBT chunklen 1228 ignored: ASCII: ˙Œ ˛.£.....................................................................xúÌ⁄ÕO.e.¿q†+[(¶›≠†e›7ñÖewy[X..ˆ.,`€ã..„—ƒì'„Õˇ¬ìQˇ..&&’õQ! 1±$^‰b¿¢&FkjbS=ÿÒ˜ÃÏ.3≥3À™5≥îÔì|“6°Àº|˚õg⁄vuùÌu˛úœw#..másŸª±¬ÃV HEX: FACECAFE007FA3160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000789CEDDACD4F1C651CC071A02B5B28A6DDADA065DD379685 mkBT chunklen 2741 ignored: ASCII: ˙Œ ˛.Ø~....................................................................xúÌùçë€8.FSH.I!)$ç§ê.íFRHnêõwÛÓ.H…YØ.€x3û’Íá§..¢.ê˙˘s.Üa.Üa.Üa.ÜaxIæˇ˛€Ô«è.ˇ.ª'Ué{ó·Ÿ˘·√oøØ_ø˛™˜⁄æg˝W9™.√˚źœ‚ˆñ˛πª∂é’o' HEX: FACECAFE007FAF7E0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000789CED9D8D91DB380C4653481A492129248DA49014924652 mkBT chunklen 1712 ignored: ASCII: ˙Œ ˛.«z....................................................................xúÌŸ…o.e.Äqh.JRJõ&i‘f≥≥5Œ‚‘Ÿú≈â„@◊øÄfiÄ...‹ê∏p‚ƒÅ.á¬.Ñ∏@%N@.®êr@i/–à.¢ç. Z®(K.T√˚Õ÷o∆flLº5¶“cÈßî.€„yfio<3µ˙.¶,€¸§ïúÀX…l∆JdOXâY1 HEX: FACECAFE007FC77A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000789CEDD9C96F1B65188071681A4A524A9B2669D466B3B335 mkBT chunklen 10775 ignored: ASCII: ˙Œ ˛.‘....................................................................xúÌ}+∏Ï(÷ˆíH,.âƒ"ëH$..âƒ"#ëX$..âçåååç,ôQ˚úÓûûÈ˘‘ˇ‘àZsÈ>U{◊.∞.Ôª.TÊÁ}6û.⁄≥ë-∆◊F`á£√.p]≈.k.flÖ~√Ûb.‡Å...∂..–û$¡w›ìŸ±Ñ¡ÒÕ|s¸ÒCo HEX: FACECAFE007FD4F00000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000789CED7D2BB8EC28D6F692482C1289C422914824161989C4 mkBT chunklen 5018 ignored: ASCII: ˙Œ ˛.Ä.ô....................................................................xúÌùkl\G.«cØ.ΫÆ.ªfiáÌıkÌuσN⁄4±„∏N‹í&-V≈á.D’.U.AiÖ...•Ç∂..‘.. ® |™.UHU´"ÒPAÿdë‚.a.µ$i)m.'§i̓i”P‚Ä9Á>gÊŒ‹{◊qvÆ„9“»â#EsÁwœ9ˇs HEX: FACECAFE00801F990000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000789CED9D6B6C5C4715C763AF1FEBC7AE1FBBDE87EDF56BED!tEXtSoftwareGraphicConverter (Intel)wIDATx\yXSg{Νyә?3U E6Ed'a_DDTA@;,ZVt\l.Pk;֩Uj@{$09}s~缿O'u_ܻ7h㵫{{{==gzz#kΞ=7o 7'U`~yǻx7mo,,C2!KR YRB @UQ=ڨnwo??yƛ7{~ ̿^ݪݰqӀPRyJȔm\UW Ҫv( 2#K+ !"/B4MǎI$G3S>v1K$פK!Q@@ -,} 0KXrO;+)A?;Xn -W9b4T]WRdTCf vl;PM5B):t̬K}זW dH+@KVjSs@޳9RW2O2tƍ뾏 Y e4=b\R\Tzk&ߦâc8HUjkWX*U-n4(\bP#|iw\Ϗ$PysC3fhY2ee-^Pk2e++2x<"wy,ZQIY%4Z1:XA"b%W3ݻNcl+ IG2ց{d$xS` G o$.aRސs[g/?3bXEkT;`Er.8‹`Lw %y|G-xD :f7(5ddfX*@? &lCm UJ:"bbYѨlAVWËa   _X!"Sy\U#3/'~= n9 l7n$i LSlC yaUEw6,{@H!bLaa&ǀxx'k8hb3$t=ꎠ pOZ֭2kl@(x^vU-(|.IŅ*- L J润NjoBC>}";(&Hel7XxBB3($kBYXB u "|M`R`FGGxS[d 8ͶP1  y<0u5Du*7w|\fHNQd*Q=x0g(Xj0Y f1F^M$qY+(*Gl;i:Sp㒩]@큡\ wLΜ M$h7!8(l}CuLJ ;LDxP P>YXGS% <>JL[ I-Co//0] /.DZ={GR@]X ʍrnUC.cBA^!+;`Osɂ+E&WVDIP-ԫA#md0S]q| %ŝ 4ɞgFZP94vHɯK/7 !|J$+9b|/~0{WL% 6M IDOFv~KˠS% ^z%&A`:Xq T%q1`EY۶]!C`VK i؏g"0VAt TMFz`lH,)'b=q]_ { y#M .JIƼ572$ˆFd]g:xc"B.ԟ"BGλn̯SJe ]L+" ei%~qx̧)(X[b+a|LL]xi .`E(e!Tu>e`ZCGy-zG[':v \' OL`M҃d s[΂b+MoONH)0N> mmA]χ ئ@xb*dInmN*j4#Νe"8f2,W|_W\arM{ຶ93z9Enm/k4^0A#MZZ4]_׈Uט2:qh V.}#GYN@ch]W 0ۦ*^uУNAoOQ`\:;_aH( nF69QDӫ.g,3ZC#a\V^Usddx$}%ͳ&p%kSTlXyDϔ+,gHm U'۷{ʆ;Rs g~(hLM|tq&S'm;f)h]F@[3h S3!+R䗵2Z鉹`sC` 4.[n"x4xF;[wUGqRlK{EO ML̀ytfe"8s'C<pPJ4|F`_(Wg|1~By_PjR,t$8/%TSHJ滹P!뢤jk1,tҢnmhc ѣRrGAd;B⓵j2SaXzR%O n3!6H*oXD)2E2w3 ߟ[W_N3<~D5&d><TC8cs凞tѫ~ slSת~T,Ya$=(Qu=YFLŞ=?h[KA kH88KN ҩ҂d3/Vw'}J`=K#BfD lXeQ0ǝ2*FB s]ab碝D0;&ET+XǵJK 'DsLTO 7-}%)qhN nDb:8/ +?O:ύ2iAZgt1W<]cUk 6SiXp,GǎhNipc͆ Yrn}aC; a "I !($)هsD8xX )t2ZlzVt.O wlDSN3 P( U'^$#m4G@Œ \KShl_| ɇ310H}}JlE-'J3)!ۄtq WZwoЖ)AqʚXQYjyP qI$Œ AԬV_[U[L"0>o\<֟q30:?N}QUR^9MB]Q/fE-tһE}ﯬ' Wf wm:-*dyHXh1 epw;5=?SL|o; % H/z(?DPPT2zǔ駗/_ZBU\d6о̓'ݷv8oVŎ OUj (1Ty'?dSoXǘ hQ.wCJ_Wn@ϊ$D%Tu'4Ώ(xЬ{fg=r;74˪jG @RTUA2W@yuHcή]g>:aǕ˗h48qcӌc[ZJ(w8x ӓ橦cyc|IENDB`cecilia5-5.4.1/scripts/builder_OSX.sh000077500000000000000000000204331372272363700174220ustar00rootroot00000000000000##################################### # Cecilia5 OSX standalone application # builder script. # # Olivier Belanger, 2020 ##################################### export DMG_DIR="Cecilia5 5.4.1" export DMG_NAME="Cecilia5_5.4.1.dmg" python3.7 setup.py py2app --plist=scripts/info.plist rm -rf build mv dist Cecilia5_OSX if cd Cecilia5_OSX; then find . -name .git -depth -exec rm -rf {} \ find . -name *.pyc -depth -exec rm -f {} \ find . -name .* -depth -exec rm -f {} \; else echo "Something wrong. Cecilia5_OSX not created" exit; fi rm Cecilia5.app/Contents/Resources/Cecilia5.ico rm Cecilia5.app/Contents/Resources/CeciliaFileIcon5.ico # keep only 64-bit arch ditto --rsrc --arch x86_64 Cecilia5.app Cecilia5-x86_64.app rm -rf Cecilia5.app mv Cecilia5-x86_64.app Cecilia5.app # Fixed wrong path in Info.plist cd Cecilia5.app/Contents awk '{gsub("@executable_path/../Frameworks/Python.framework/Versions/2.7/Python", "@executable_path/../Frameworks/Python.framework/Versions/3.7/Python")}1' Info.plist > Info.plist_tmp && mv Info.plist_tmp Info.plist awk '{gsub("Library/Frameworks/Python.framework/Versions/3.7/bin/python3.7", "@executable_path/../Frameworks/Python.framework/Versions/3.7/Python")}1' Info.plist > Info.plist_tmp && mv Info.plist_tmp Info.plist awk '{gsub("/usr/local/bin/python3.7", "@executable_path/../Frameworks/Python.framework/Versions/3.7/Python")}1' Info.plist > Info.plist_tmp && mv Info.plist_tmp Info.plist install_name_tool -change @loader_path/libwx_osx_cocoau_core-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_core-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_core.so install_name_tool -change @loader_path/libwx_baseu_net-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu_net-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_core.so install_name_tool -change @loader_path/libwx_baseu-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_core.so #install_name_tool -change @loader_path/libwx_osx_cocoau_adv-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_adv-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_adv.so install_name_tool -change @loader_path/libwx_osx_cocoau_core-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_core-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_adv.so install_name_tool -change @loader_path/libwx_baseu_net-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu_net-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_adv.so install_name_tool -change @loader_path/libwx_baseu-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_adv.so install_name_tool -change @loader_path/libwx_osx_cocoau_html-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_html-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_html.so install_name_tool -change @loader_path/libwx_osx_cocoau_core-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_core-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_html.so install_name_tool -change @loader_path/libwx_baseu_net-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu_net-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_html.so install_name_tool -change @loader_path/libwx_baseu-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_html.so install_name_tool -change @loader_path/libwx_osx_cocoau_html-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_html-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_richtext.so install_name_tool -change @loader_path/libwx_osx_cocoau_core-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_core-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_richtext.so install_name_tool -change @loader_path/libwx_baseu_net-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu_net-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_richtext.so install_name_tool -change @loader_path/libwx_baseu-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_richtext.so install_name_tool -change @loader_path/libwx_osx_cocoau_richtext-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_richtext-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_richtext.so #install_name_tool -change @loader_path/libwx_osx_cocoau_adv-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_adv-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_richtext.so install_name_tool -change @loader_path/libwx_osx_cocoau_stc-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_stc-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_stc.so install_name_tool -change @loader_path/libwx_osx_cocoau_core-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_core-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_stc.so install_name_tool -change @loader_path/libwx_baseu_net-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu_net-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_stc.so install_name_tool -change @loader_path/libwx_baseu-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_stc.so install_name_tool -change @loader_path/libwx_baseu_xml-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu_xml-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_xml.so install_name_tool -change @loader_path/libwx_osx_cocoau_core-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_core-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_xml.so install_name_tool -change @loader_path/libwx_baseu_net-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu_net-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_xml.so install_name_tool -change @loader_path/libwx_baseu-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/_xml.so install_name_tool -change @loader_path/libwx_osx_cocoau_core-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_osx_cocoau_core-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/siplib.so install_name_tool -change @loader_path/libwx_baseu_net-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu_net-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/siplib.so install_name_tool -change @loader_path/libwx_baseu-3.1.4.0.0.dylib @loader_path/../../../../../Frameworks/libwx_baseu-3.1.4.0.0.dylib Resources/lib/python3.7/lib-dynload/wx/siplib.so install_name_tool -change @loader_path/libportaudio.2.dylib @loader_path/../../../../../Frameworks/libportaudio.2.dylib Resources/lib/python3.7/lib-dynload/pyo/_pyo.so install_name_tool -change @loader_path/libportmidi.dylib @loader_path/../../../../../Frameworks/libportmidi.dylib Resources/lib/python3.7/lib-dynload/pyo/_pyo.so install_name_tool -change @loader_path/liblo.7.dylib @loader_path/../../../../../Frameworks/liblo.7.dylib Resources/lib/python3.7/lib-dynload/pyo/_pyo.so install_name_tool -change @loader_path/libsndfile.1.dylib @loader_path/../../../../../Frameworks/libsndfile.1.dylib Resources/lib/python3.7/lib-dynload/pyo/_pyo.so install_name_tool -change @loader_path/libportaudio.2.dylib @loader_path/../../../../../Frameworks/libportaudio.2.dylib Resources/lib/python3.7/lib-dynload/pyo/_pyo64.so install_name_tool -change @loader_path/libportmidi.dylib @loader_path/../../../../../Frameworks/libportmidi.dylib Resources/lib/python3.7/lib-dynload/pyo/_pyo64.so install_name_tool -change @loader_path/liblo.7.dylib @loader_path/../../../../../Frameworks/liblo.7.dylib Resources/lib/python3.7/lib-dynload/pyo/_pyo64.so install_name_tool -change @loader_path/libsndfile.1.dylib @loader_path/../../../../../Frameworks/libsndfile.1.dylib Resources/lib/python3.7/lib-dynload/pyo/_pyo64.so cd ../../.. cp -R Cecilia5_OSX/Cecilia5.app . echo "assembling DMG..." mkdir "$DMG_DIR" cd "$DMG_DIR" cp -R ../Cecilia5.app . ln -s /Applications . cd .. hdiutil create "$DMG_NAME" -srcfolder "$DMG_DIR" rm -rf "$DMG_DIR" rm -rf Cecilia5_OSX rm -rf Cecilia5.app cecilia5-5.4.1/scripts/cecilia_crashes000066400000000000000000000037621372272363700177360ustar00rootroot00000000000000Debian stretch - python 3.5.3 - wxpython phoenix 3.0.3 (gtk2) dev2828 - jack1 ============================================================================= 1 - At init (when creating a menu separator). - Try to remove all AppendSeparator() calls. * Seems to be fixed by replacing AppendSeparator() with Append(wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR) 2 - On play (pthread_cond_wait in libjack.so). Just before setting sr and bufsize. * Fixed with macros Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS 3 - When destroying the interface. 4 - On start/stop button, segmentation fault related to wx.Timer.Notify(). 5 - Use after free in pthread_create (from libjack.so). * Appears to be fixed in glibc-2.25 (2.24 actually installed on Debian stretch). Debian stretch - python 3.5.3 - wxpython phoenix 3.0.3 (gtk2) dev2828 - portaudio ================================================================================= 1 - On stop (from Pa_AbortStream() from pyo) at src/engine/ad_portaudio.c:305 err = 32767 2 - On play Thread 1 "python3" received signal SIGSEGV, Segmentation fault. 0x0000555555722ce0 in PyType_IsSubtype () (gdb) bt full #0 0x0000555555722ce0 in PyType_IsSubtype () No symbol table info available. #1 0x00005555556bfaa2 in ?? () No symbol table info available. #2 0x00005555556c028e in PyArg_ParseTupleAndKeywords () No symbol table info available. #3 0x00007fffea36de8d in Server_init (self=0x555556b41540, args=0x7fffd7e84388, kwds=0x0) at src/engine/servermodule.c:615 OSX 10.8.5 - python 3.5.3 - wxpython phoenix 3.0.3 dev2828 - portaudio ====================================================================== 1 - Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libportaudio.2.dylib 0x0000000104ca6ffd Pa_IsStreamActive + 28 1 _pyo.cpython-35m-darwin.so 0x000000010494a956 Server_pa_deinit + 22 2 _pyo.cpython-35m-darwin.so 0x0000000104942781 Server_boot + 273cecilia5-5.4.1/scripts/info.plist000066400000000000000000000051161372272363700167150ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleDisplayName Cecilia5 CFBundleDocumentTypes LSIsAppleDefaultForType CFBundleTypeIconFile CeciliaFileIcon5.icns CFBundleTypeExtensions c5 CFBundleTypeOSTypes TEXT CFBundleTypeRole Editor CFBundleExecutable Cecilia5 CFBundleIconFile Cecilia5.icns CFBundleIdentifier org.pythonmac.unspecified.Cecilia5 CFBundleInfoDictionaryVersion 6.0 CFBundleName Cecilia5 CFBundlePackageType APPL CFBundleShortVersionString 5.4.0 CFBundleSignature ???? CFBundleVersion 5.4.0 LSHasLocalizedDisplayName NSAppleScriptEnabled NSHumanReadableCopyright Copyright not specified NSMainNibFile MainMenu NSPrincipalClass NSApplication PyMainFileNames __boot__ PyOptions alias argv_emulation no_chdir optimize 0 prefer_ppc site_packages use_pythonpath PyResourcePackages PyRuntimeLocations @executable_path/../Frameworks/Python.framework/Versions/2.7/Python PythonInfoDict PythonExecutable @executable_path/../Frameworks/Python.framework/Versions/2.7/Python PythonLongVersion 2.7.8 (v2.7.8:ee879c0ffa11, Jun 29 2014, 21:07:35) \n[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] PythonShortVersion 2.7 py2app alias template app version 0.9 cecilia5-5.4.1/scripts/old_stuff.py000066400000000000000000000035151372272363700172450ustar00rootroot00000000000000def removeDuplicates(seq): result = [] for item in seq: if item not in result: result.append(item) return result ###### Interpolation functions ###### def interpolate(lines, size, listlen): scale = size / lines[-1][0] templist = [] for i in range(listlen - 1): t = lines[i + 1][0] - lines[i][0] num = int(round(t * scale)) if num == 0: pass else: step = (lines[i + 1][1] - lines[i][1]) / num for j in range(num): templist.append(lines[i][1] + (step * j)) return templist def interpolateCurved(lines, size, listlen): lines = removeDuplicates(lines) scale = size / float(len(lines)) depth = int(round(scale)) off = int(1. / (scale - depth)) templist = [] for i in range(len(lines) - 1): num = depth if (i % off) == 0: num = depth + 1 step = (lines[i + 1][1] - lines[i][1]) / num for j in range(num): templist.append(lines[i][1] + (step * j)) return templist def interpolateLog(lines, size, listlen, yrange): scale = size / lines[-1][0] templist = [] for i in range(listlen - 1): t = lines[i + 1][0] - lines[i][0] num = int(round(t * scale)) if num == 0: pass else: step = (lines[i + 1][1] - lines[i][1]) / num for j in range(num): templist.append(lines[i][1] + (step * j)) list = [] if yrange[0] == 0: yoffrange = .00001 else: yoffrange = yrange[0] totalRange = yrange[1] - yoffrange currentTotalRange = math.log10(yrange[1] / yrange[0]) currentMin = math.log10(yrange[0]) for p in templist: if p == 0: p = .00001 ratio = (p - yoffrange) / totalRange list.append(math.pow(10, ratio * currentTotalRange + currentMin)) return list cecilia5-5.4.1/scripts/release_src.sh000077500000000000000000000011271372272363700175310ustar00rootroot00000000000000#! /bin/bash # # 1. change version number # 2. Execute from cecilia5 folder : ./scripts/release_src.sh # version=5.4.1 replace=XXX doc_rep=Cecilia5_XXX-doc doc_tar=Cecilia5_XXX-doc.tar.bz2 src_rep=Cecilia5_XXX-src src_tar=Cecilia5_XXX-src.tar.bz2 cp -R ./doc-en/build ./doc-en/${doc_rep/$replace/$version} cd doc-en tar -cjvf ${doc_tar/$replace/$version} ${doc_rep/$replace/$version} rm -R ${doc_rep/$replace/$version} cd .. git checkout-index -a -f --prefix=${src_rep/$replace/$version}/ tar -cjvf ${src_tar/$replace/$version} ${src_rep/$replace/$version} rm -R ${src_rep/$replace/$version} cecilia5-5.4.1/scripts/saveOldPresetAsXmlFiles.py000066400000000000000000000036511372272363700217710ustar00rootroot00000000000000import os import xmlrpc.client as xmlrpclib PRESETS_DELIMITER = "####################################\n" \ "##### Cecilia reserved section #####\n" \ "#### Presets saved from the app ####\n" \ "####################################\n" root = "/home/olivier/git/cecilia5/Resources/modules/" folders = os.listdir(root) presets_folder = "/home/olivier/.cecilia5/presets/" for folder in folders: files = os.listdir(os.path.join(root, folder)) for file in files: mylocals = {} rewrite = False with open(os.path.join(root, folder, file), "r") as f: text = f.read() if PRESETS_DELIMITER in text: rewrite = True newtext = text[:text.find(PRESETS_DELIMITER) - 1] text = text[text.find(PRESETS_DELIMITER) + len(PRESETS_DELIMITER) + 1:] if text.strip() == "": print("No presets...") continue exec(text, globals(), mylocals) fileName = os.path.splitext(file)[0] if not os.path.isdir(os.path.join(presets_folder, fileName)): os.mkdir(os.path.join(presets_folder, fileName)) for preset in mylocals["CECILIA_PRESETS"]: for key in list(mylocals["CECILIA_PRESETS"][preset]["plugins"]): mylocals["CECILIA_PRESETS"][preset]["plugins"][str(key)] = mylocals["CECILIA_PRESETS"][preset]["plugins"][key] del mylocals["CECILIA_PRESETS"][preset]["plugins"][key] msg = xmlrpclib.dumps((mylocals["CECILIA_PRESETS"][preset], ), allow_none=True) with open(os.path.join(presets_folder, fileName, preset), "w") as fw: fw.write(msg) if rewrite: with open(os.path.join(root, folder, file), "w") as f: f.write(newtext) cecilia5-5.4.1/scripts/testcolours.c5000066400000000000000000000046231372272363700175260ustar00rootroot00000000000000class Module(BaseModule): """ Module's documentation """ def __init__(self): BaseModule.__init__(self) self.snd = self.addSampler("snd") self.out = Mix(self.snd, voices=self.nchnls, mul=self.env) Interface = [ csampler(name="snd"), cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"), cslider(name="gain1", label="green1", min=-90, max=18, init=0, rel="lin", unit="dB", col="green1"), cslider(name="gain2", label="green2", min=-90, max=18, init=0, rel="lin", unit="dB", col="green2"), cslider(name="gain3", label="green3", min=-90, max=18, init=0, rel="lin", unit="dB", col="green3"), cslider(name="gain4", label="green4", min=-90, max=18, init=0, rel="lin", unit="dB", col="green4"), cslider(name="gain9", label="blue1", min=-90, max=18, init=0, rel="lin", unit="dB", col="blue1"), cslider(name="gain10", label="blue2", min=-90, max=18, init=0, rel="lin", unit="dB", col="blue2"), cslider(name="gain11", label="blue3", min=-90, max=18, init=0, rel="lin", unit="dB", col="blue3"), cslider(name="gain12", label="blue4", min=-90, max=18, init=0, rel="lin", unit="dB", col="blue4"), cslider(name="gain5", label="red1", min=-90, max=18, init=0, rel="lin", unit="dB", col="red1"), cslider(name="gain13", label="red2", min=-90, max=18, init=0, rel="lin", unit="dB", col="red2"), cslider(name="gain14", label="red3", min=-90, max=18, init=0, rel="lin", unit="dB", col="red3"), cslider(name="gain15", label="red4", min=-90, max=18, init=0, rel="lin", unit="dB", col="red4"), cslider(name="gain6", label="orange1", min=-90, max=18, init=0, rel="lin", unit="dB", col="orange1"), cslider(name="gain7", label="orange2", min=-90, max=18, init=0, rel="lin", unit="dB", col="orange2"), cslider(name="gain8", label="orange3", min=-90, max=18, init=0, rel="lin", unit="dB", col="orange3"), cslider(name="gain16", label="orange4", min=-90, max=18, init=0, rel="lin", unit="dB", col="orange4"), cslider(name="gain26", label="purple1", min=-90, max=18, init=0, rel="lin", unit="dB", col="purple1"), cslider(name="gain27", label="purple2", min=-90, max=18, init=0, rel="lin", unit="dB", col="purple2"), cslider(name="gain28", label="purple3", min=-90, max=18, init=0, rel="lin", unit="dB", col="purple3"), cslider(name="gain29", label="purple4", min=-90, max=18, init=0, rel="lin", unit="dB", col="purple4"), ] cecilia5-5.4.1/scripts/trackDownRemainingObjRefs.py000066400000000000000000000004211372272363700223120ustar00rootroot00000000000000# In CeciliaInterface.onClose() - not finished to add cleanup() method to all widgets. # to track down remaining references to an object. import sys, gc print(sys.getrefcount(self)) referers = gc.get_referrers(self) for referer in referers: print() print(referer) cecilia5-5.4.1/scripts/win_installer.iss000066400000000000000000000044771372272363700203100ustar00rootroot00000000000000; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! [Setup] ; NOTE: The value of AppId uniquely identifies this application. ; Do not use the same AppId value in installers for other applications. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) AppId={{A970BBE5-4FA8-496E-9823-2491D09DA043} AppName=Cecilia5 AppVersion=5.4.1 AppPublisher=iACT.umontreal.ca AppPublisherURL=http://ajaxsoundstudio.com/software/cecilia/ AppSupportURL=https://github.com/belangeo/cecilia5 AppUpdatesURL=http://ajaxsoundstudio.com/software/cecilia/ DefaultDirName={pf}\Cecilia5 DisableDirPage=yes DefaultGroupName=Cecilia5 AllowNoIcons=yes LicenseFile=C:\Users\Admin\git\cecilia5\Cecilia5_Win\Resources\COPYING.txt OutputBaseFilename=Cecilia5_5.4.1_setup Compression=lzma SolidCompression=yes ChangesAssociations=yes Uninstallable=yes [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] Source: "C:\Users\Admin\git\cecilia5\Cecilia5_Win\Cecilia5.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "C:\Users\Admin\git\cecilia5\Cecilia5_Win\Resources\*"; DestDir: "{app}\Resources"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Registry] Root: HKCR; Subkey: ".c5"; ValueType: string; ValueName: ""; ValueData: "Cecilia5File"; Flags: uninsdeletevalue Root: HKCR; Subkey: "Cecilia5File"; ValueType: string; ValueName: ""; ValueData: "Cecilia 5 File"; Flags: uninsdeletevalue Root: HKCR; Subkey: "Cecilia5File\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\Resources\CeciliaFileIcon5.ico" Root: HKCR; Subkey: "Cecilia5File\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\Cecilia5.exe"" ""%1""" [Icons] Name: "{group}\Cecilia5"; Filename: "{app}\Cecilia5.exe"; WorkingDir: "{app}" Name: "{commondesktop}\Cecilia5"; Filename: "{app}\Cecilia5.exe"; Tasks: desktopicon [Run] Filename: "{app}\Cecilia5.exe"; Description: "{cm:LaunchProgram,Cecilia5}"; Flags: nowait postinstall skipifsilent [UninstallDelete] Type: files; Name: "{app}\Cecilia5 Uninstall" cecilia5-5.4.1/setup.py000066400000000000000000000010141372272363700147210ustar00rootroot00000000000000""" This is a setup.py script generated by py2applet Usage: python3.6 setup.py py2app """ from setuptools import setup APP = ['Cecilia5.py'] APP_NAME = 'Cecilia5' DATA_FILES = ['Resources/'] OPTIONS = {'argv_emulation': False, #'strip': False, # only for debugging purposes. 'iconfile': 'Resources/Cecilia5.icns', 'includes': 'wx.adv,wx.html,wx.xml'} setup( name=APP_NAME, app=APP, data_files=DATA_FILES, options={'py2app': OPTIONS}, setup_requires=['py2app'], ) cecilia5-5.4.1/whatsnew.md000066400000000000000000000204041372272363700153750ustar00rootroot00000000000000# Changes # ## Version 5.4.1 ## - Uses latest fixes to portmidi for 64-bit Windows. - Upgraded WxPython to version 4.1.0. - Upgraded pyo to version 1.0.3. ## Version 5.4.0 ## - Last fixes. - Official release. ## Version 5.3.9 ## - Various bug fixes. - Added filter per grain in StochGrains.c5 an StochGrains2.c5. - Six new modules. Filters/AutoModFilter, Filters/Binaural, Filter/StateVar2 ResonatorsVerbs/MatrixReverb, Synthesis/WaveScanSynth and Time/Stutterer. ## Version 5.3.8 ## - Fixed crash when opening the application when .cecilia5 folder does not exist. - Fixed recording audio output twice (or more) on linux and Windows. ## Version 5.3.7 ## - Track down every single segmentation fault! - Presets are no more saved in .c5 files. They are saved in a folder, named as the module, in ".cecilia5/presets/". ## Version 5.3.6 ## - Upgraded to python version 3.7.4 (64-bit), wxPython-4.0.6 and pyo 1.0.1. ## Version 5.3.5 ## - Upgraded to python version 3.6.4, wxPython-4.0.1 and pyo 0.9.0. - Better error handling when loading or running a cecilia5 module. ## Version 5.3.4 ## - Upgraded to python version 3.6.3, wxPython-4.0.0b2 and pyo 0.8.8. - Fixed preferences path encoding on Windows. - Fixed unicode paths on MacOS when running as an application. ## Version 5.3.3 ## - Upgraded to python version 3.6.2, wxPython-4.0.0b1 and pyo 0.8.7. ## Version 5.3.2 ## - Build against wxPython-4.0.0a2. ## Version 5.3.1 ## - Better handling of non-ascii characters in preferences file. - Fixed standalone packaging on OSX. - Fixed curved line not showing on module initialization. - UltimateGrainer now allows a grain duration up to 10 seconds. ## Version 5.3.0 ## - Migration of the application code to python3 and wxpython 3.0.3 (phoenix) completed. - Source code cleanup (refactoring, removed dead and/or duplicated code). - Lot of fixed bugs. - Automatic saving of the module after creating or deleting a preset. - Warning to save on quit. - The app now opens with the last used module (instead of the random chooser). - Revisited tooltip and html documentation. - On Windows, default preferences for input/output devices should be set to WASAPI devices. - Added ChenLee attractor to the ChaosMod plugin. - Added various modulation waveforms to BinModulator.c5 - All modules have been tested and cleaned. New modules: - UltimateGrainer - A state-of-the-art granulation processing module. - RandomAccumulator - Variable speed recording accumulator module. - UpDistoRes - Arctangent distortion module with upsampling and resonant lowpass filter. ## Version 5.0.8 ## - Record button now records in realtime (offline rendering is now triggered with Menubar->Action->Bounce to Disk. - User can set the starting point by moving the cursor above the grapher. - Drag and Drop file or folder on the input sound popup to loads sounds. - Right-click on sound popup opens a "Recent audio files" popup. - Sliders can be controlled with MIDI or Open Sound Control messages. - When moving a point on the graph, Alt key clipped the position on the horizontal axis while Shift-Alt keys clipped the position on the vertical axis. - Added two batch processing modes. Either every presets applied on the selected sound, or the current preset applied to every sounds in the folder. - Midi notes are automatically assigned to the sampler transposition and controller 7 to the master gain. Must be activated in the preferences. ## Version 5.0.7 ## - Disabled duration slider while playing. - Fixed segmentation fault on preset changes. - Added a DropFileTarget on the Grapher (for .c5 or .py files). - Fixed opening soundfile player/editor on Windows and OSX. - Changed delay time before a popup close itself (when loosing focus) from 500 ms to 1000 ms. ## Version 5.0.6 ## - Fixed memory leak occuring on each run play/stop (need pyo [revision 974](https://code.google.com/p/cecilia5/source/detail?r=974)). - Allow fraction notation in cgen (list entry in the interface). ## Version 5.0.5 ## - Disabled printing sound info for all sounds in the selected folder. - Added new filter module, Vocoder. (need pyo to be up-to-date with sources). - Fixed wrong executable path generated by py2app (OSX app). - Removed Jack and Coreaudio from driver's list in bundled app on OSX (leaved them when running from sources). - Fixed bug: The red button doesn't turn off at the end of recording. - cgen ignores trailing coma in poup entry. - Fixed bug: Do not quiery for the control panel if the interface doesn't exist yet. - Fixed bug in ListEntry widget when loading from a preset. ## Version 5.0.3 ## - List in cgen popup window can now be entered as 'comma' or 'space' separated values. - Fixed number of channels used by the audio Server. - Wait for the audio Server releasing soundcard's stream before allowing to play again. - Fixed saving .c5 file on Windows. - Added 2 filter modules: BrickWall.c5 and BandBrickWall.c5. ## Version 5.0.2 ## - Automatically save a preset named "last save" when saving a module. On module loading, if "last save" preset exists, it is activated. - Fixed display of the Channels menu. - Fixed bugs in sliders automation recording. ## Version 5.0.1 ## - Fixed audio input/output selection. - Fixed BaseModule.addSampler "pitch" argument. ## Version 5.0.0 ## First beta release. ######################################################################################### Version 5.0.9: - L'outil de polyphonie, dans la section des popup menus, offre maintenant une sélection d'accords, plutôt que seulement des vitesses de lecture au hasard. - Ajout d'un spectrogramme temps-réel à l'application. - Multiple modes pour le signal source d'un module (icône juste à droite du menu de sélection du son source). -- mode 0 (icône de fichier): mode standard, par la lecture d'un fichier son -- mode 1 (icône de micro): Entrée micro temps-réel, les paramètres du sampler sont ignorés. -- mode 2 (icône de micro avec un "1"): La mémoire du sampler est remplie par le signal provenant de l'entrée micro, une seule fois au début de la performance. Ensuite, le sampler boucle le signal selon les paramètres définis dans l'interface. -- mode 3 (icône de micro entouré de flèches): La mémoire du sampler est remplie, en continu, par le signal provenant de l'entrée micro. Une mémoire à double tampons est utilisée pour éviter les clics. Le sampler agit normalement sur un signal audio constamment renouvelé. ** Les mode 1 et 3 ne sont pas disponibles pour les modules nécéssitant un son en RAM comme origine du processus (exemple les modules de granulation). - Ajout d'une adresse d'envoi OSC aux sliders, pour communiquer la valeurs d'initialisation à une interface externe au début de la performance. - Ajout de l'option menu "Use Sound Duration on Folder Batch Processing". La durée de chacun des sons contrôle la durée de l'export du son traité. - Optimization du rafraichissement graphique du Grapher. - Possibilité de modifier en temps réel, à l'aide des flèches au dessus du menu de sélection, l'ordre des plugins dans l'onglet "Port-Processing". - Sans changer le nombre de canaux audio du processus, l'utilisateur peut changer, via les préférences, les premières entrée/sortie physiques (sur la carte-son) utilisées. - Ajout du contrôle MIDI sur les sliders de la fenêtre du sampler (Clic droit sur le nom du paramètre pour lancer l'assignation d'un potentiomètre. Shift+Clic droit pour annuler l'assignation courante). - Ajout du contrôle OSC sur les sliders de la fenêtre du sampler (Double-clic sur le nom du paramètre pour ouvrir la fenêtre de configuration). - Nouveaux formats audio supportés: FLAC, OGG, SD2, AU, CAF. Nouveaux modules: - ResonatorsVerbs/ConvolutionReverb : Réverbe par convolution. - Time/Pelletizer : Module de granulation où la fréquence de génération des grains, la hauteur et la durée sont indépendantes. - Pitch/LooperBank : Jusqu'à 500 lectures en boucle simultannées, avec chacune leur random propre sur la hauteur et l'amplitude. - Catégorie "Spectral", 13 modules de manipulations spectrales basées sur le vocodeur de phase. - Amélioration d'un grand nombre de modules déjà présents. Nouveau Plugin: - ChaosMod : Modulation en anneaux à l'aide d'attracteurs chaotique. Version 5.1.0: Version 5.2.0: Version 5.2.1: