├── Makefile ├── README.md ├── emit_c.py ├── main.c ├── pd_compile.py └── pdom.py /Makefile: -------------------------------------------------------------------------------- 1 | # Hey Emacs, this is a -*- makefile -*- 2 | 3 | # Makefile for generating C code for an example patch, and compiling 4 | # the C code into an executable. 5 | # 6 | # Copyright (C) 2013 Jared Boone, ShareBrained Technology, Inc. 7 | # 8 | # This file is part of PD compiler. 9 | # 10 | # This program is free software; you can redistribute it and/or 11 | # modify it under the terms of the GNU General Public License 12 | # as published by the Free Software Foundation; either version 2 13 | # of the License, or (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program; if not, write to the Free Software 22 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 23 | # 02110-1301, USA. 24 | 25 | PYTHON_SRC = pd_compile.py pdom.py emit_c.py 26 | C_SRC = main.c out.c 27 | 28 | PATCHFILE=web-pure-data/unittests/subtract~.pd 29 | 30 | all: main 31 | 32 | main: $(C_SRC) 33 | gcc -std=c99 -O2 -o main $(C_SRC) 34 | 35 | out.c: $(PYTHON_SRC) 36 | python pd_compile.py $(PATCHFILE) 37 | 38 | clean: 39 | rm -f main 40 | rm -f out.c 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pd_compiler 2 | =========== 3 | 4 | Pure Data compiler, translates pd files into C code for compilation on embedded systems. 5 | Presently targeting ARM Cortex-M4F microcontrollers. 6 | 7 | Status 8 | ====== 9 | 10 | This project is in a very early stage of development. It can only comprehend and compile 11 | patches comprised of a very limited set of objects -- all of them audio-rate objects. 12 | 13 | Example Output 14 | ============== 15 | 16 | Using an example "A01.sinewave.pd" that (I think) comes with Pure Data, here is the 17 | generated C code. 18 | 19 | ```C 20 | #include 21 | #include 22 | 23 | static const float SAMPLING_RATE = 44100.0f; 24 | 25 | static float object_0_phase = 0.0f; 26 | static FILE* object_1_file = NULL; 27 | 28 | void init() { 29 | object_0_phase = 0.0f; 30 | object_1_file = fopen("dac_1.f32", "wb"); 31 | } 32 | 33 | void dsptick() { 34 | const float object_0_outlet_0 = cosf(object_0_phase); 35 | const float object_0_phase_increment = 440.000000f * 2.0f * M_PI / SAMPLING_RATE; 36 | object_0_phase = fmodf(object_0_phase + object_0_phase_increment, 2.0f * M_PI); 37 | const float object_3_outlet_0 = object_0_outlet_0 * 0.050000f; 38 | const float object_1_buffer[2] = { object_3_outlet_0, 0.0f }; 39 | fwrite(object_1_buffer, sizeof(object_1_buffer), 1, object_1_file); 40 | } 41 | 42 | void deinit() { 43 | fclose(object_1_file); 44 | object_1_file = NULL; 45 | } 46 | ``` 47 | 48 | Requirements 49 | ============ 50 | 51 | To run pd_compile.py, you will need: 52 | 53 | * [Peak-Rules](https://pypi.python.org/pypi/PEAK-Rules), which is used for conditionally 54 | binding visitor functions to classes of Pure Data objects. These visitor functions 55 | generate C code that is run at different times over the PD patch lifespan. 56 | 57 | 58 | Usage 59 | ===== 60 | 61 | Run pd_compile.py with a single argument, the name of a Pure Data patch file. A bunch of 62 | parsing and debugging junk will be printed, along with the generated C code. The 63 | generated C code is also placed in a file called "out.c". 64 | 65 | There is also a Makefile which will generate C code for a Pure Data patch file, and 66 | compile it with a wrapper main() function into an executable "main" that generates three 67 | seconds worth of audio into a zero or more dac_*.f32 files (containing 32-bit float 68 | sample data). The number of dac_*.f32 files is determined by the number of DAC objects 69 | in the patch file. 70 | 71 | This is totally half-baked test code, if you couldn't tell... 72 | 73 | License 74 | ======= 75 | 76 | Copyright (C) 2011 Jared Boone, ShareBrained Technology, Inc. 77 | 78 | This program is free software; you can redistribute it and/or 79 | modify it under the terms of the GNU General Public License 80 | as published by the Free Software Foundation; either version 2 81 | of the License, or (at your option) any later version. 82 | 83 | This program is distributed in the hope that it will be useful, 84 | but WITHOUT ANY WARRANTY; without even the implied warranty of 85 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 86 | GNU General Public License for more details. 87 | 88 | You should have received a copy of the GNU General Public License 89 | along with this program; if not, write to the Free Software 90 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 91 | 02110-1301, USA. 92 | 93 | Contact 94 | ======= 95 | 96 | ShareBrained Technology, Inc. 97 | 98 | -------------------------------------------------------------------------------- /emit_c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Visitor functions for Pure Data (pd) object model which produce C code. 4 | # 5 | # Copyright (C) 2013 Jared Boone, ShareBrained Technology, Inc. 6 | # 7 | # This file is part of PD compiler. 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 22 | # 02110-1301, USA. 23 | 24 | from pdom import * 25 | from peak.rules import when 26 | 27 | # TODO: Maybe instead of passing back language-specific strings, pass back statements... 28 | # initialize variable, assignments, ??? 29 | 30 | def declare(o): 31 | return tuple() 32 | 33 | def init(o): 34 | return tuple() 35 | 36 | def dsptick_start(o): 37 | return tuple() 38 | 39 | def dsptick(o): 40 | return tuple() 41 | 42 | def dsptick_end(o): 43 | return tuple() 44 | 45 | def deinit(o): 46 | return tuple() 47 | 48 | ####################################### 49 | def obj_prop(obj, name): 50 | return 'object_%s_%s' % (obj.id, name) 51 | 52 | def outlet_str(outlet): 53 | return obj_prop(outlet.parent, 'outlet_%d' % outlet.id) 54 | 55 | def source_str(inlet): 56 | source = inlet.source 57 | if isinstance(source, Outlet): 58 | return outlet_str(source) 59 | elif isinstance(source, ConstantOutlet): 60 | return '%ff' % source.value 61 | else: 62 | raise Exception('Unknown source %s' % source) 63 | 64 | ####################################### 65 | @when(declare, AudioDAC) 66 | def audio_dac_declare(o): 67 | return ('static FILE* %s = NULL;' % (obj_prop(o, 'file'),), 68 | ) 69 | 70 | @when(init, AudioDAC) 71 | def audio_dac_init(o): 72 | return ('%s = fopen("dac_%d.f32", "wb");' % (obj_prop(o, 'file'), o.id), 73 | ) 74 | 75 | @when(dsptick, AudioDAC) 76 | def audio_dac_dsptick(o): 77 | left = source_str(o._left) if o._left.source else '0.0f' 78 | right = source_str(o._right) if o._right.source else '0.0f' 79 | return ('const float %s[2] = { %s, %s };' % (obj_prop(o, 'buffer'), left, right), 80 | 'fwrite(%s, sizeof(%s), 1, %s);' % (obj_prop(o, 'buffer'), obj_prop(o, 'buffer'), obj_prop(o, 'file')), 81 | ) 82 | 83 | @when(deinit, AudioDAC) 84 | def audio_dac_deinit(o): 85 | return ('fclose(%s);' % (obj_prop(o, 'file'),), 86 | '%s = NULL;' % (obj_prop(o, 'file'),), 87 | ) 88 | 89 | ####################################### 90 | @when(declare, 'isinstance(o, AudioOscillator)') 91 | def osc_declare(o): 92 | return ('static float %s = 0.0f;' % (obj_prop(o, 'phase'),), 93 | ) 94 | 95 | @when(init, 'isinstance(o, AudioOscillator)') 96 | def osc_init(o): 97 | return ('%s = 0.0f;' % (obj_prop(o, 'phase'),), 98 | ) 99 | 100 | @when(dsptick, 'isinstance(o, AudioOscillator)') 101 | def osc_dsptick(o): 102 | outlet = outlet_str(o._out) 103 | result = ('const float %s = cosf(%s);' % (outlet, obj_prop(o, 'phase')), 104 | 'const float %s = %s * 2.0f * M_PI / SAMPLING_RATE;' % (obj_prop(o, 'phase_increment'), source_str(o._in)), 105 | '%s = fmodf(%s + %s, 2.0f * M_PI);' % (obj_prop(o, 'phase'), obj_prop(o, 'phase'), obj_prop(o, 'phase_increment')), 106 | ) 107 | return result 108 | 109 | ####################################### 110 | @when(declare, 'isinstance(o, AudioPhasor)') 111 | def phasor_declare(o): 112 | return ('static float %s = 0.0f;' % (obj_prop(o, 'phase'),), 113 | ) 114 | 115 | @when(init, 'isinstance(o, AudioPhasor)') 116 | def phasor_init(o): 117 | return ('%s = 0.0f;' % (obj_prop(o, 'phase'),), 118 | ) 119 | 120 | @when(dsptick, 'isinstance(o, AudioPhasor)') 121 | def phasor_dsptick(o): 122 | outlet = outlet_str(o._out) 123 | result = ('const float %s = %s;' % (outlet, obj_prop(o, 'phase')), 124 | 'const float %s = %s / SAMPLING_RATE;' % (obj_prop(o, 'phase_increment'), source_str(o._in)), 125 | '%s = fmodf(%s + %s, 1.0f);' % (obj_prop(o, 'phase'), obj_prop(o, 'phase'), obj_prop(o, 'phase_increment')), 126 | ) 127 | return result 128 | 129 | ####################################### 130 | def _unfn(o, function_str, scale_str=''): 131 | outlet = outlet_str(o._out) 132 | arg = source_str(o._in) 133 | return ('const float %s = %s(%s%s);' % (outlet, function_str, arg, scale_str), 134 | ) 135 | 136 | def _binop(o, operator_str): 137 | outlet = outlet_str(o._out) 138 | left = source_str(o._in1) 139 | right = source_str(o._in2) 140 | return ('const float %s = %s %s %s;' % (outlet, left, operator_str, right), 141 | ) 142 | 143 | def _binfn(o, function_str): 144 | outlet = outlet_str(o._out) 145 | left = source_str(o._in1) 146 | right = source_str(o._in2) 147 | return ('const float %s = %s(%s, %s);' % (outlet, function_str, left, right), 148 | ) 149 | 150 | ####################################### 151 | @when(dsptick, 'isinstance(o, AudioAdd)') 152 | def add_dsptick(o): 153 | return _binop(o, '+') 154 | 155 | @when(dsptick, 'isinstance(o, AudioSubtract)') 156 | def subtract_dsptick(o): 157 | return _binop(o, '-') 158 | 159 | @when(dsptick, 'isinstance(o, AudioMultiply)') 160 | def multiply_dsptick(o): 161 | return _binop(o, '*') 162 | 163 | @when(dsptick, 'isinstance(o, AudioDivide)') 164 | def divide_dsptick(o): 165 | return _binop(o, '/') 166 | 167 | ####################################### 168 | @when(dsptick, 'isinstance(o, AudioCosine)') 169 | def cos_dsptick(o): 170 | return _unfn(o, 'cosf', ' * 2.0f * M_PI') 171 | 172 | @when(dsptick, 'isinstance(o, AudioAbsolute)') 173 | def abs_dsptick(o): 174 | return _unfn(o, 'fabsf') 175 | 176 | @when(dsptick, 'isinstance(o, AudioExponent)') 177 | def exp_dsptick(o): 178 | return _unfn(o, 'expf') 179 | 180 | @when(dsptick, 'isinstance(o, AudioSignal)') 181 | def sig_dsptick(o): 182 | outlet = outlet_str(o._out) 183 | arg = source_str(o._in) 184 | return ('const float %s = %s;' % (outlet, arg), 185 | ) 186 | 187 | @when(dsptick, 'isinstance(o, AudioWrap)') 188 | def wrap_dsptick(o): 189 | outlet = outlet_str(o._out) 190 | arg = source_str(o._in) 191 | return ('const float %s = (%s > 0.0f) ? (%s - (int)%s) : (%s - ((int)%s - 1.0f));' % (outlet, 192 | arg, arg, arg, arg, arg), 193 | ) 194 | 195 | ####################################### 196 | @when(dsptick, 'isinstance(o, AudioMaximum)') 197 | def max_dsptick(o): 198 | return _binfn(o, 'fmaxf') 199 | 200 | @when(dsptick, 'isinstance(o, AudioMinimum)') 201 | def min_dsptick(o): 202 | return _binfn(o, 'fminf') 203 | 204 | @when(dsptick, 'isinstance(o, AudioPower)') 205 | def pow_dsptick(o): 206 | return _binfn(o, 'powf') 207 | 208 | ####################################### 209 | @when(dsptick, 'isinstance(o, AudioClip)') 210 | def clip_dsptick(o): 211 | outlet = outlet_str(o._out) 212 | i = source_str(o._in) 213 | lo = source_str(o._lo) 214 | hi = source_str(o._hi) 215 | return ('const float %s = (%s < %s) ? %s : (%s > %s) ? %s : %s;' % (outlet, 216 | i, lo, lo, i, hi, hi, i), 217 | ) 218 | 219 | @when(dsptick, 'isinstance(o, AudioLogarithm)') 220 | def log_dsptick(o): 221 | outlet = outlet_str(o._out) 222 | in1 = source_str(o._in1) 223 | if o._in2.source: 224 | in2 = source_str(o._in2) 225 | return ('const float %s = logf(%s) / logf(%s);' % (outlet, in1, in2), 226 | ) 227 | else: 228 | return ('const float %s = logf(%s);' % (outlet, in1), 229 | ) 230 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Wrapper main() for compiling generated C code into an executable. 3 | * 4 | * Copyright (C) 2013 Jared Boone, ShareBrained Technology, Inc. 5 | * 6 | * This file is part of PD compiler. 7 | * 8 | * This program is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU General Public License 10 | * as published by the Free Software Foundation; either version 2 11 | * of the License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 21 | * 02110-1301, USA. 22 | */ 23 | 24 | #include 25 | 26 | extern void init(); 27 | extern void dsptick(); 28 | extern void deinit(); 29 | 30 | int main(int argc, char* argv[]) { 31 | int i=0; 32 | 33 | init(); 34 | for(i=0; i<44100 * 3; i++) { 35 | dsptick(); 36 | } 37 | deinit(); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /pd_compile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Pure Data (pd) compiler front-end 4 | # 5 | # Copyright (C) 2013 Jared Boone, ShareBrained Technology, Inc. 6 | # 7 | # This file is part of PD compiler. 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 22 | # 02110-1301, USA. 23 | 24 | import sys 25 | import os.path 26 | 27 | import pdom 28 | 29 | input_file_path = sys.argv[1] 30 | input_file_base, input_file_extension = os.path.splitext(input_file_path) 31 | output_file_path = input_file_base + '.c' 32 | 33 | parse_context = pdom.parse_patch(open(input_file_path, 'r')) 34 | 35 | chain = [] 36 | def walk_object_inlets(o): 37 | global chain 38 | for inlet in o.inlet: 39 | outlet = inlet.source 40 | if outlet and outlet.parent: 41 | walk_object_inlets(outlet.parent) 42 | if o not in chain: 43 | chain.append(o) 44 | 45 | dacs = tuple((o for o in parse_context.objects if isinstance(o, pdom.AudioDAC))) 46 | for dac in dacs: 47 | walk_object_inlets(dac) 48 | 49 | #print(chain) 50 | #print 51 | 52 | from emit_c import declare, init, dsptick_start, dsptick, dsptick_end, deinit 53 | 54 | lines = ['#include ', 55 | '#include ', 56 | '', 57 | 'static const float SAMPLING_RATE = 44100.0f;', 58 | '', 59 | ] 60 | 61 | for o in chain: 62 | lines.extend(declare(o)) 63 | lines.append('') 64 | 65 | lines.append('void init() {') 66 | for o in chain: 67 | lines.extend(('\t%s' %s for s in init(o))) 68 | lines.append('}') 69 | lines.append('') 70 | 71 | lines.append('void dsptick() {') 72 | for o in chain: 73 | lines.extend(('\t%s' % s for s in dsptick_start(o))) 74 | for o in chain: 75 | lines.extend(('\t%s' % s for s in dsptick(o))) 76 | for o in chain: 77 | lines.extend(('\t%s' % s for s in dsptick_end(o))) 78 | lines.append('}') 79 | lines.append('') 80 | 81 | lines.append('void deinit() {') 82 | for o in chain: 83 | lines.extend(('\t%s' % s for s in deinit(o))) 84 | lines.append('}') 85 | 86 | c_code = '\n'.join(lines) 87 | c_file = open(output_file_path, 'w') 88 | c_file.write(c_code) 89 | c_file.close() 90 | -------------------------------------------------------------------------------- /pdom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Pure Data (pd) object model classes. 4 | # 5 | # Copyright (C) 2013 Jared Boone, ShareBrained Technology, Inc. 6 | # 7 | # This file is part of PD compiler. 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 22 | # 02110-1301, USA. 23 | 24 | class Message(object): 25 | def __init__(self, content): 26 | self._content = content 27 | 28 | def __repr__(self): 29 | return '%s(%s)' % (self.__class__.__name__, self._content) 30 | 31 | class Text(object): 32 | def __init__(self, comment): 33 | self._comment = comment 34 | 35 | def __repr__(self): 36 | return '%s(%s)' % (self.__class__.__name__, self._comment) 37 | 38 | class Connect(object): 39 | def __init__(self, source, outlet, target, inlet): 40 | self._source = int(source) 41 | self._outlet = int(outlet) 42 | self._target = int(target) 43 | self._inlet = int(inlet) 44 | 45 | @property 46 | def source_index(self): 47 | return self._source 48 | 49 | @property 50 | def source_outlet_index(self): 51 | return self._outlet 52 | 53 | @property 54 | def target_index(self): 55 | return self._target 56 | 57 | @property 58 | def target_inlet_index(self): 59 | return self._inlet 60 | 61 | def __repr__(self): 62 | return '%s(%s,%s,%s,%s)' % (self.__class__.__name__, 63 | self._source, self._outlet, self._target, self._inlet) 64 | 65 | class UnsupportedObject(object): 66 | def __init__(self, name, parameters): 67 | self._name = name 68 | self._parameters = parameters 69 | 70 | def __repr__(self): 71 | return '%s(%s,%s)' % (self.__class__.__name__, self._name, self._parameters) 72 | 73 | class Inlet(object): 74 | def __init__(self, parent, value_type): 75 | self._parent = parent 76 | self._value_type = value_type 77 | self._source = None 78 | 79 | @property 80 | def id(self): 81 | return self._parent.inlet.index(self) 82 | 83 | @property 84 | def source(self): 85 | return self._source 86 | 87 | @source.setter 88 | def source(self, value): 89 | self._source = value 90 | 91 | class Outlet(object): 92 | def __init__(self, parent): 93 | self._parent = parent 94 | 95 | @property 96 | def id(self): 97 | return self._parent.outlet.index(self) 98 | 99 | @property 100 | def parent(self): 101 | return self._parent 102 | 103 | class ConstantOutlet(object): 104 | def __init__(self, value): 105 | self._value = value 106 | 107 | @property 108 | def parent(self): 109 | return None 110 | 111 | @property 112 | def value(self): 113 | return self._value 114 | 115 | class DSPOperator(object): 116 | def __init__(self, **kwargs): 117 | self._inlets = tuple() 118 | self._outlets = tuple() 119 | self.id = None 120 | 121 | @property 122 | def inlet(self): 123 | return self._inlets 124 | 125 | @property 126 | def outlet(self): 127 | return self._outlets 128 | 129 | def __repr__(self): 130 | return self.__class__.__name__ 131 | 132 | class AudioDAC(DSPOperator): 133 | def __init__(self, parameters, **kwargs): 134 | super(AudioDAC, self).__init__(**kwargs) 135 | 136 | self._left = Inlet(self, float) 137 | self._right = Inlet(self, float) 138 | self._inlets = (self._left, self._right) 139 | 140 | class AudioPhasor(DSPOperator): 141 | def __init__(self, parameters, **kwargs): 142 | super(AudioPhasor, self).__init__(**kwargs) 143 | 144 | self._in = Inlet(self, float) 145 | self._ft1 = Inlet(self, float) 146 | self._inlets = (self._in, self._ft1,) 147 | 148 | self._out = Outlet(self) 149 | self._outlets = (self._out,) 150 | 151 | if len(parameters) > 0: 152 | self._in.source = ConstantOutlet(float(parameters[0])) 153 | 154 | class AudioOscillator(DSPOperator): 155 | def __init__(self, parameters, **kwargs): 156 | super(AudioOscillator, self).__init__(**kwargs) 157 | 158 | self._in = Inlet(self, float) 159 | self._ft1 = Inlet(self, float) 160 | self._inlets = (self._in, self._ft1,) 161 | 162 | self._out = Outlet(self) 163 | self._outlets = (self._out,) 164 | 165 | if len(parameters) > 0: 166 | self._in.source = ConstantOutlet(float(parameters[0])) 167 | 168 | class AudioMultiply(DSPOperator): 169 | def __init__(self, parameters, **kwargs): 170 | super(AudioMultiply, self).__init__(**kwargs) 171 | 172 | self._in1 = Inlet(self, float) 173 | self._in2 = Inlet(self, float) 174 | self._inlets = (self._in1, self._in2) 175 | 176 | self._out = Outlet(self) 177 | self._outlets = (self._out,) 178 | 179 | if len(parameters) > 0: 180 | self._in2.source = ConstantOutlet(float(parameters[0])) 181 | 182 | class AudioDivide(DSPOperator): 183 | def __init__(self, parameters, **kwargs): 184 | super(AudioDivide, self).__init__(**kwargs) 185 | 186 | self._in1 = Inlet(self, float) 187 | self._in2 = Inlet(self, float) 188 | self._inlets = (self._in1, self._in2) 189 | 190 | self._out = Outlet(self) 191 | self._outlets = (self._out,) 192 | 193 | if len(parameters) > 0: 194 | self._in2.source = ConstantOutlet(float(parameters[0])) 195 | 196 | class AudioAdd(DSPOperator): 197 | def __init__(self, parameters, **kwargs): 198 | super(AudioAdd, self).__init__(**kwargs) 199 | 200 | self._in1 = Inlet(self, float) 201 | self._in2 = Inlet(self, float) 202 | self._inlets = (self._in1, self._in2) 203 | 204 | self._out = Outlet(self) 205 | self._outlets = (self._out,) 206 | 207 | if len(parameters) > 0: 208 | self._in2.source = ConstantOutlet(float(parameters[0])) 209 | 210 | class AudioSubtract(DSPOperator): 211 | def __init__(self, parameters, **kwargs): 212 | super(AudioSubtract, self).__init__(**kwargs) 213 | 214 | self._in1 = Inlet(self, float) 215 | self._in2 = Inlet(self, float) 216 | self._inlets = (self._in1, self._in2) 217 | 218 | self._out = Outlet(self) 219 | self._outlets = (self._out,) 220 | 221 | if len(parameters) > 0: 222 | self._in2.source = ConstantOutlet(float(parameters[0])) 223 | 224 | class AudioMaximum(DSPOperator): 225 | def __init__(self, parameters, **kwargs): 226 | super(AudioMaximum, self).__init__(**kwargs) 227 | 228 | self._in1 = Inlet(self, float) 229 | self._in2 = Inlet(self, float) 230 | self._inlets = (self._in1, self._in2) 231 | 232 | self._out = Outlet(self) 233 | self._outlets = (self._out,) 234 | 235 | if len(parameters) > 0: 236 | self._in2.source = ConstantOutlet(float(parameters[0])) 237 | 238 | class AudioMinimum(DSPOperator): 239 | def __init__(self, parameters, **kwargs): 240 | super(AudioMinimum, self).__init__(**kwargs) 241 | 242 | self._in1 = Inlet(self, float) 243 | self._in2 = Inlet(self, float) 244 | self._inlets = (self._in1, self._in2) 245 | 246 | self._out = Outlet(self) 247 | self._outlets = (self._out,) 248 | 249 | if len(parameters) > 0: 250 | self._in2.source = ConstantOutlet(float(parameters[0])) 251 | 252 | class AudioPower(DSPOperator): 253 | def __init__(self, parameters, **kwargs): 254 | super(AudioPower, self).__init__(**kwargs) 255 | 256 | self._in1 = Inlet(self, float) 257 | self._in2 = Inlet(self, float) 258 | self._inlets = (self._in1, self._in2) 259 | 260 | self._out = Outlet(self) 261 | self._outlets = (self._out,) 262 | 263 | if len(parameters) > 0: 264 | self._in2.source = ConstantOutlet(float(parameters[0])) 265 | 266 | class AudioLogarithm(DSPOperator): 267 | def __init__(self, parameters, **kwargs): 268 | super(AudioLogarithm, self).__init__(**kwargs) 269 | 270 | self._in1 = Inlet(self, float) 271 | self._in2 = Inlet(self, float) 272 | self._inlets = (self._in1, self._in2) 273 | 274 | self._out = Outlet(self) 275 | self._outlets = (self._out,) 276 | 277 | if len(parameters) > 0: 278 | self._in2.source = ConstantOutlet(float(parameters[0])) 279 | 280 | class AudioAbsolute(DSPOperator): 281 | def __init__(self, parameters, **kwargs): 282 | super(AudioAbsolute, self).__init__(**kwargs) 283 | 284 | self._in = Inlet(self, float) 285 | self._inlets = (self._in,) 286 | 287 | self._out = Outlet(self) 288 | self._outlets = (self._out,) 289 | 290 | class AudioExponent(DSPOperator): 291 | def __init__(self, parameters, **kwargs): 292 | super(AudioExponent, self).__init__(**kwargs) 293 | 294 | self._in = Inlet(self, float) 295 | self._inlets = (self._in,) 296 | 297 | self._out = Outlet(self) 298 | self._outlets = (self._out,) 299 | 300 | class AudioWrap(DSPOperator): 301 | def __init__(self, parameters, **kwargs): 302 | super(AudioWrap, self).__init__(**kwargs) 303 | 304 | self._in = Inlet(self, float) 305 | self._inlets = (self._in,) 306 | 307 | self._out = Outlet(self) 308 | self._outlets = (self._out,) 309 | 310 | class AudioCosine(DSPOperator): 311 | def __init__(self, parameters, **kwargs): 312 | super(AudioCosine, self).__init__(**kwargs) 313 | 314 | self._in = Inlet(self, float) 315 | self._phase = Inlet(self, float) 316 | self._inlets = (self._in, self._phase,) 317 | 318 | self._out = Outlet(self) 319 | self._outlets = (self._out,) 320 | 321 | class AudioSignal(DSPOperator): 322 | def __init__(self, parameters, **kwargs): 323 | super(AudioSignal, self).__init__(**kwargs) 324 | 325 | self._in = Inlet(self, float) 326 | self._inlets = (self._in,) 327 | 328 | self._out = Outlet(self) 329 | self._outlets = (self._out,) 330 | 331 | if len(parameters) > 0: 332 | self._in.source = ConstantOutlet(float(parameters[0])) 333 | 334 | class AudioClip(DSPOperator): 335 | def __init__(self, parameters, **kwargs): 336 | super(AudioClip, self).__init__(**kwargs) 337 | 338 | self._in = Inlet(self, float) 339 | self._lo = Inlet(self, float) 340 | self._hi = Inlet(self, float) 341 | self._inlets = (self._in, self._lo, self._hi) 342 | 343 | self._out = Outlet(self) 344 | self._outlets = (self._out,) 345 | 346 | if len(parameters) > 0: 347 | self._lo.source = ConstantOutlet(float(parameters[0])) 348 | if len(parameters) > 1: 349 | self._hi.source = ConstantOutlet(float(parameters[1])) 350 | 351 | class Print(object): 352 | def __init__(self, parameters): 353 | pass 354 | 355 | object_constructor = { 356 | 'dac~': AudioDAC, 357 | 'phasor~': AudioPhasor, 358 | 'osc~': AudioOscillator, 359 | '+~': AudioAdd, 360 | '-~': AudioSubtract, 361 | '*~': AudioMultiply, 362 | '/~': AudioDivide, 363 | 'max~': AudioMaximum, 364 | 'min~': AudioMinimum, 365 | 'abs~': AudioAbsolute, 366 | 'exp~': AudioExponent, 367 | 'wrap~': AudioWrap, 368 | 'pow~': AudioPower, 369 | 'log~': AudioLogarithm, 370 | 'cos~': AudioCosine, 371 | 'sig~': AudioSignal, 372 | 'clip~': AudioClip, 373 | 'print': Print, 374 | } 375 | 376 | def connect_parser(text): 377 | args = text.split() 378 | source, outlet, target, inlet = args 379 | new_connect = Connect(source, outlet, target, inlet) 380 | return new_connect 381 | 382 | def msg_parser(text): 383 | args = text.split(None, 2) 384 | x_pos = args[0] 385 | y_pos = args[1] 386 | content = args[2] 387 | return Message(content) 388 | 389 | def obj_parser(text): 390 | args = text.split() 391 | x_pos = args[0] 392 | y_pos = args[1] 393 | object_name = args[2] # TODO: optional! 394 | parameters = args[3:] 395 | new_object = object_constructor[object_name](parameters) 396 | return new_object 397 | 398 | def text_parser(text): 399 | args = text.split(None, 2) 400 | x_pos = args[0] 401 | y_pos = args[1] 402 | comment = args[2] 403 | return Text(comment) 404 | 405 | element_type_parser = { 406 | 'connect': connect_parser, 407 | 'msg': msg_parser, 408 | 'obj': obj_parser, 409 | 'text': text_parser, 410 | } 411 | 412 | class ParseContext(object): 413 | def __init__(self): 414 | self.connects = [] 415 | self.objects = [] 416 | 417 | def chunk_parser_regular_element(self, text): 418 | element_type, text = text.split(None, 1) 419 | new_object = element_type_parser[element_type](text) 420 | if isinstance(new_object, Connect): 421 | self.connects.append(new_object) 422 | else: 423 | self.objects.append(new_object) 424 | 425 | def chunk_parser_array_data(self, text): 426 | return None 427 | 428 | def chunk_parser_frameset(self, text): 429 | return None 430 | 431 | chunk_parser = { 432 | 'A': chunk_parser_array_data, 433 | 'N': chunk_parser_frameset, 434 | 'X': chunk_parser_regular_element, 435 | } 436 | 437 | def parse_record(self, text): 438 | chunk_type = text[0] 439 | return self.chunk_parser[chunk_type](self, text[1:].strip()) 440 | 441 | def parse_patch(self, patch_file): 442 | patch = patch_file.read() 443 | patch_file.close() 444 | 445 | patch_split = patch.split('\\;') 446 | patch_split = map(lambda s: s.split(';'), patch_split) 447 | records = [] 448 | for split_items in patch_split: 449 | if records: 450 | records[-1] += ';' + split_items[0] 451 | records.extend(split_items[1:]) 452 | else: 453 | records = split_items 454 | records = map(str.strip, records) 455 | #print('\n'.join(records)) 456 | 457 | for record in records: 458 | if len(record) and record[0] == '#': 459 | self.parse_record(record[1:]) 460 | 461 | for i in range(len(self.objects)): 462 | o = self.objects[i] 463 | o.id = i 464 | #print('%d: %s' % (i, o)) 465 | 466 | for i in range(len(self.connects)): 467 | c = self.connects[i] 468 | source_object = self.objects[c.source_index] 469 | outlet = source_object.outlet[c.source_outlet_index] 470 | target_object = self.objects[c.target_index] 471 | inlet = target_object.inlet[c.target_inlet_index] 472 | inlet.source = outlet 473 | #print('%d: %s' % (i, c)) 474 | 475 | def parse_patch(f): 476 | context = ParseContext() 477 | context.parse_patch(f) 478 | return context 479 | --------------------------------------------------------------------------------