fluidsynth~.c 14.2 KB
Newer Older
porres's avatar
readme    
porres committed
1
2
3
4

// This is a modification of https://github.com/porres/pd-fluid


porres's avatar
porres committed
5
/*
porres's avatar
porres committed
6
7
8
9
Copyright: Alexandre Torres Porres, based on the work of
 Frank Barknecht, Jonathan Wilkes and Albert Gräf
 Distributed under the GPLv2+, please see LICENSE below.

porres's avatar
porres committed
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
LICENSE:
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 2 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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
porres's avatar
porres committed
25
26
27
28
29

#include "m_pd.h"
#include <fluidsynth.h>
#include <string.h>
#include <unistd.h>
porres's avatar
porres committed
30
31
32

#define MAXSYSEXSIZE 1024 // Size of sysex data list (excluding the F0 [240] and F7 [247] bytes)

porres's avatar
porres committed
33
34
35
static t_class *fluid_tilde_class;
 
typedef struct _fluid_tilde{
porres's avatar
porres committed
36
37
38
39
40
41
42
    t_object            x_obj;
    fluid_synth_t      *x_synth;
    fluid_settings_t   *x_settings;
    t_outlet           *x_out_left;
    t_outlet           *x_out_right;
    t_canvas           *x_canvas;
    int                 x_sysex;
porres's avatar
gen    
porres committed
43
    int                 x_count;
porres's avatar
porres committed
44
    int                 x_ready;
porres's avatar
porres committed
45
    t_atom              x_at[MAXSYSEXSIZE];
porres's avatar
porres committed
46
47
48
    unsigned char       x_type;
    unsigned char       x_data;
    unsigned char       x_channel;
porres's avatar
porres committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
}t_fluid_tilde;

t_int *fluid_tilde_perform(t_int *w){
    t_fluid_tilde *x = (t_fluid_tilde *)(w[1]);
    t_sample *left = (t_sample *)(w[2]);
    t_sample *right = (t_sample *)(w[3]);
    int n = (int)(w[4]);
    fluid_synth_write_float(x->x_synth, n, left, 0, 1, right, 0, 1);
    return(w+5);
}

static void fluid_tilde_dsp(t_fluid_tilde *x, t_signal **sp){
    dsp_add(fluid_tilde_perform, 4, x, sp[0]->s_vec, sp[1]->s_vec, (t_int)sp[0]->s_n);
}

static void fluid_tilde_free(t_fluid_tilde *x){
    if(x->x_synth)
        delete_fluid_synth(x->x_synth);
    if(x->x_settings)
        delete_fluid_settings(x->x_settings);
}

porres's avatar
porres committed
71
72
static void fluid_info(void){
    post(" - [fluidsynth~] uses fluidsynth version: %s ", FLUIDSYNTH_VERSION);
porres's avatar
porres committed
73
    //            post("[fluidsynth~]: loaded soundfont %s", realname);
porres's avatar
porres committed
74
75
76
    post("\n");
}

porres's avatar
porres committed
77
static void fluid_note(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
78
79
80
    s = NULL;
    if(x->x_synth == NULL)
        return;
porres's avatar
porres committed
81
    if(ac == 2 || ac == 3){
porres's avatar
porres committed
82
83
        int key = atom_getintarg(0, ac, av);
        int vel = atom_getintarg(1, ac, av);
porres's avatar
porres committed
84
        int chan = ac > 2 ? atom_getintarg(2, ac, av) : 1;
porres's avatar
porres committed
85
        fluid_synth_noteon(x->x_synth, chan-1, key, vel);
porres's avatar
porres committed
86
87
88
    }
}

porres's avatar
porres committed
89
static void fluid_program_change(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
90
91
92
    s = NULL;
    if(x->x_synth == NULL)
        return;
porres's avatar
porres committed
93
94
95
    if(ac == 1 || ac == 2){
        int prog = atom_getintarg(0, ac, av);
        int chan = ac > 1 ? atom_getintarg(1, ac, av) : 1;
porres's avatar
porres committed
96
97
98
99
        fluid_synth_program_change(x->x_synth, chan-1, prog);
    }
}

porres's avatar
porres committed
100
static void fluid_control_change(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
101
102
103
    s = NULL;
    if(x->x_synth == NULL)
        return;
porres's avatar
porres committed
104
105
106
107
    if(ac == 2 || ac == 3){
        int ctrl = atom_getintarg(0, ac, av);
        int val = atom_getintarg(1, ac, av);
        int chan = ac > 2 ? atom_getintarg(2, ac, av) : 1;
porres's avatar
porres committed
108
109
110
111
        fluid_synth_cc(x->x_synth, chan-1, ctrl, val);
    }
}

porres's avatar
porres committed
112
static void fluid_pitch_bend(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
113
114
115
    s = NULL;
    if(x->x_synth == NULL)
        return;
porres's avatar
porres committed
116
117
118
    if(ac == 1 || ac == 2){
        int val = atom_getintarg(0, ac, av);
        int chan = ac > 1 ? atom_getintarg(1, ac, av) : 1;
porres's avatar
porres committed
119
120
121
122
        fluid_synth_pitch_bend(x->x_synth, chan-1, val);
    }
}

porres's avatar
porres committed
123
static void fluid_bank(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
124
125
126
    s = NULL;
    if(x->x_synth == NULL)
        return;
porres's avatar
porres committed
127
128
129
    if(ac == 1 || ac == 2){
        int bank = atom_getintarg(0, ac, av);
        int chan = ac > 1 ? atom_getintarg(1, ac, av) : 1;
porres's avatar
gen    
porres committed
130
//        post("bank (%d) chan (%d)", bank, chan);
porres's avatar
porres committed
131
132
133
134
        fluid_synth_bank_select(x->x_synth, chan-1, bank);
    }
}

porres's avatar
gen    
porres committed
135
static void fluid_gen(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
136
137
138
    s = NULL;
    if(x->x_synth == NULL)
        return;
porres's avatar
porres committed
139
    if(ac == 3){
porres's avatar
porres committed
140
141
        int chan, param;
        float value;
porres's avatar
porres committed
142
143
        chan = atom_getintarg(0, ac, av);
        param = atom_getintarg(1, ac, av);
porres's avatar
porres committed
144
        value = atom_getfloatarg(2, ac, av);
porres's avatar
porres committed
145
        fluid_synth_set_gen(x->x_synth, chan-1, param, value);
porres's avatar
porres committed
146
        // https://github.com/uliss/pure-data/blob/ceammc/ceammc/ext/src/misc/fluid.cpp#L390
porres's avatar
porres committed
147
    }
porres's avatar
gen    
porres committed
148
}
porres's avatar
porres committed
149

porres's avatar
gen    
porres committed
150
static void fluid_aftertouch(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
151
152
153
    s = NULL;
    if(x->x_synth == NULL)
        return;
porres's avatar
porres committed
154
155
156
    if(ac == 1 || ac == 2){
        int val = atom_getintarg(0, ac, av);
        int chan = ac > 1 ? atom_getintarg(1, ac, av) : 1;
porres's avatar
porres committed
157
158
159
160
        fluid_synth_channel_pressure(x->x_synth, chan-1, val);
    }
}

porres's avatar
porres committed
161
static void fluid_polytouch(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
162
163
164
    s = NULL;
    if(x->x_synth == NULL)
        return;
porres's avatar
porres committed
165
166
167
    if(ac == 2 || ac == 3){
        int val = atom_getintarg(0, ac, av);
        int key = atom_getintarg(1, ac, av);
porres's avatar
porres committed
168
        int chan = ac > 2 ? atom_getintarg(2, ac, av) : 1;
porres's avatar
porres committed
169
        fluid_synth_key_pressure(x->x_synth, chan-1, key, val);
porres's avatar
porres committed
170
171
172
    }
}

porres's avatar
porres committed
173
static void fluid_sysex(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
174
    s = NULL;
porres's avatar
porres committed
175
    if(x->x_synth == NULL)
porres's avatar
porres committed
176
        return;
porres's avatar
porres committed
177
    if(ac > 0){
porres's avatar
porres committed
178
179
        char buf[MAXSYSEXSIZE];
        int len = 0;
porres's avatar
porres committed
180
181
        while(len < MAXSYSEXSIZE && len < ac){
            buf[len] = atom_getintarg(len, ac, av);
porres's avatar
porres committed
182
183
184
185
186
187
188
189
190
            len++;
        }
        // TODO: In order to handle bulk dump requests in the future, we will
        // have to pick up fluidsynth's response here and output that to a
        // control outlet (which doesn't exist at present).
        fluid_synth_sysex(x->x_synth, buf, len, NULL, NULL, NULL, 0);
    }
}

porres's avatar
porres committed
191
192
193
static void fluid_float(t_fluid_tilde *x, t_float f){
    if(f >= 0 && f <= 255){
        unsigned char val = (unsigned char)f;
porres's avatar
porres committed
194
        if(val > 127){ // not a data type
porres's avatar
gen    
porres committed
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
            if(val == 0xF0){ // start of sysex
                x->x_sysex = 1;
                x->x_count = 0;
            }
            else if(val == 0xF7){ // end of sysex
                fluid_sysex(x, &s_list, x->x_count, x->x_at);
                x->x_sysex = x->x_count = 0;
            }
            else{
                x->x_type = val & 0xF0; // get type
                x->x_channel = (val & 0x0F) + 1; // get channel
                x->x_ready = (x->x_type == 0xC0 || x->x_type == 0xD0); // ready if program or touch
            }
        }
        else if(x->x_sysex){
            SETFLOAT(&x->x_at[x->x_count], (t_float)val);
            x->x_count++;
porres's avatar
porres committed
212
        }
porres's avatar
gen    
porres committed
213
214
215
216
217
        else{
            if(x->x_ready){
                switch(x->x_type){
                    case 0x80: // group 128-143 (NOTE OFF)
                    SETFLOAT(&x->x_at[0], (t_float)x->x_data); // key
porres's avatar
porres committed
218
                    SETFLOAT(&x->x_at[1], 0); // make it note on with velocity 0
porres's avatar
porres committed
219
220
                    SETFLOAT(&x->x_at[2], (t_float)x->x_channel);
                    fluid_note(x, &s_list, 3, x->x_at);
porres's avatar
porres committed
221
                    break;
porres's avatar
gen    
porres committed
222
223
224
                case 0x90: // group 144-159 (NOTE ON)
                    SETFLOAT(&x->x_at[0], (t_float)x->x_data); // key
                    SETFLOAT(&x->x_at[1], val); // velocity
porres's avatar
porres committed
225
226
                    SETFLOAT(&x->x_at[2], (t_float)x->x_channel);
                    fluid_note(x, &s_list, 3, x->x_at);
porres's avatar
porres committed
227
                    break;
porres's avatar
gen    
porres committed
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
                case 0xA0: // group 160-175 (POLYPHONIC AFTERTOUCH)
                    SETFLOAT(&x->x_at[0], val); // aftertouch pressure value
                    SETFLOAT(&x->x_at[1], (t_float)x->x_data); // key
                    SETFLOAT(&x->x_at[2], (t_float)x->x_channel);
                    fluid_polytouch(x, &s_list, 3, x->x_at);
                    break;
                case 0xB0: // group 176-191 (CONTROL CHANGE)
                    SETFLOAT(&x->x_at[0], val); // control value
                    SETFLOAT(&x->x_at[1], (t_float)x->x_data); // control number
                    SETFLOAT(&x->x_at[2], (t_float)x->x_channel);
                    fluid_control_change(x, &s_list, 3, x->x_at);
                    break;
                case 0xC0: // group 192-207 (PROGRAM CHANGE)
                    SETFLOAT(&x->x_at[0], val); // control value
                    SETFLOAT(&x->x_at[1], (t_float)x->x_channel);
                    fluid_program_change(x, &s_list, 2, x->x_at);
                    break;
                case 0xD0: // group 208-223 (AFTERTOUCH)
                    SETFLOAT(&x->x_at[0], val); // control value
                    SETFLOAT(&x->x_at[1], (t_float)x->x_channel);
                    fluid_aftertouch(x, &s_list, 2, x->x_at);
                    break;
                case 0xE0: // group 224-239 (PITCH BEND)
porres's avatar
porres committed
251
252
253
                    SETFLOAT(&x->x_at[0], (val << 7) + x->x_data);
                    SETFLOAT(&x->x_at[1], x->x_channel);
                    fluid_pitch_bend(x, &s_list, 2, x->x_at);
porres's avatar
porres committed
254
255
256
                    break;
                default:
                    break;
porres's avatar
gen    
porres committed
257
258
259
260
261
262
                }
                x->x_type = x->x_ready = 0; // clear
            }
            else{ // not ready, get data and make it ready
                x->x_data = val;
                x->x_ready = 1;
porres's avatar
porres committed
263
264
265
266
267
268
269
            }
        }
    }
    else
        x->x_type = x->x_ready = 0; // clear
}

porres's avatar
porres committed
270
static void fluid_load(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
271
272
    s = NULL;
    if(x->x_synth == NULL){
porres's avatar
readme    
porres committed
273
        pd_error(x, "[fluidsynth~]: no fluidsynth instance created");
porres's avatar
porres committed
274
275
        return;
    }
porres's avatar
porres committed
276
277
    if(ac >= 1 && av->a_type == A_SYMBOL){
        const char* filename = atom_getsymbolarg(0, ac, av)->s_name;
porres's avatar
porres committed
278
279
280
281
282
283
284
        const char* ext = strrchr(filename, '.');
        char realdir[MAXPDSTRING], *realname = NULL;
        int fd;
        if(ext && !strchr(ext, '/')){ // extension already supplied, no default extension
            ext = "";
            fd = canvas_open(x->x_canvas, filename, ext, realdir, &realname, MAXPDSTRING, 0);
            if(fd < 0){
porres's avatar
porres committed
285
              pd_error(x, "[fluidsynth~]: can't find soundfont %s", filename);
porres's avatar
porres committed
286
287
288
289
290
291
292
293
294
295
              return;
            }
        }
        else{
            ext = ".sf2"; // let's try sf2
            fd = canvas_open(x->x_canvas, filename, ext, realdir, &realname, MAXPDSTRING, 0);
            if(fd < 0){ // failed
                ext = ".sf3"; // let's try sf3 then
                fd = canvas_open(x->x_canvas, filename, ext, realdir, &realname, MAXPDSTRING, 0);
                if(fd < 0){ // also failed
porres's avatar
porres committed
296
                    pd_error(x, "[fluidsynth~]: can't find soundfont %s", filename);
porres's avatar
porres committed
297
298
299
300
301
302
303
304
                   return;
                }
            }
        }
        // Save the current working directory.
        char buf[MAXPDSTRING], *cwd = getcwd(buf, MAXPDSTRING);
        sys_close(fd);
        chdir(realdir);
porres's avatar
porres committed
305
        if(fluid_synth_sfload(x->x_synth, realname, 0) >= 0)
porres's avatar
porres committed
306
            fluid_synth_program_reset(x->x_synth);
porres's avatar
porres committed
307
        cwd && chdir(cwd); // Restore the working directory.
porres's avatar
porres committed
308
309
310
    }
}

porres's avatar
porres committed
311
/*static void fluid_init(t_fluid_tilde *x, t_symbol *s, int ac, t_atom *av){
porres's avatar
porres committed
312
313
314
315
316
    s = NULL;
    if(x->x_synth)
        delete_fluid_synth(x->x_synth);
    if(x->x_settings)
        delete_fluid_settings(x->x_settings);
porres's avatar
porres committed
317
318
319
320
321
322
323
}*/

static void *fluid_tilde_new(t_symbol *s, int ac, t_atom *av){
    s = NULL;
    t_fluid_tilde *x = (t_fluid_tilde *)pd_new(fluid_tilde_class);
    x->x_synth = NULL;
    x->x_settings = NULL;
porres's avatar
gen    
porres committed
324
    x->x_sysex = x->x_ready = x->x_count = 0;
porres's avatar
porres committed
325
    x->x_data = x->x_channel = x->x_type = 0;
porres's avatar
porres committed
326
327
328
    x->x_out_left = outlet_new(&x->x_obj, &s_signal);
    x->x_out_right = outlet_new(&x->x_obj, &s_signal);
    x->x_canvas = canvas_getcurrent();
porres's avatar
porres committed
329
330
    x->x_settings = new_fluid_settings();
    if(x->x_settings == NULL){
porres's avatar
porres committed
331
        pd_error(x, "[fluidsynth~]: couldn't create synth settings\n");
porres's avatar
porres committed
332
        goto end;
porres's avatar
porres committed
333
334
335
336
337
    }
    else{ // load settings:
        fluid_settings_setnum(x->x_settings, "synth.midi-channels", 16);
        fluid_settings_setnum(x->x_settings, "synth.polyphony", 256);
        fluid_settings_setnum(x->x_settings, "synth.gain", 0.600000);
porres's avatar
porres committed
338
        fluid_settings_setnum(x->x_settings, "synth.sample-rate", sys_getsr());
porres's avatar
porres committed
339
340
341
342
343
        fluid_settings_setstr(x->x_settings, "synth.chorus.active", "no");
        fluid_settings_setstr(x->x_settings, "synth.reverb.active", "no");
        fluid_settings_setstr(x->x_settings, "synth.ladspa.active", "no");
        x->x_synth = new_fluid_synth(x->x_settings); // Create fluidsynth instance:
        if(x->x_synth == NULL){
porres's avatar
readme    
porres committed
344
            pd_error(x, "[fluidsynth~]: couldn't fluidsynth instance");
porres's avatar
porres committed
345
            goto end;
porres's avatar
porres committed
346
        }
porres's avatar
porres committed
347
        fluid_load(x, gensym("load"), ac, av); // try to load argument as soundfont
porres's avatar
porres committed
348
    }
porres's avatar
porres committed
349
end:
porres's avatar
porres committed
350
351
352
    return(void *)x;
}
 
porres's avatar
porres committed
353
354
void fluidsynth_tilde_setup(void){
    fluid_tilde_class = class_new(gensym("fluidsynth~"), (t_newmethod)fluid_tilde_new,
porres's avatar
porres committed
355
356
        (t_method)fluid_tilde_free, sizeof(t_fluid_tilde), CLASS_DEFAULT, A_GIMME, 0);
    class_addmethod(fluid_tilde_class, (t_method)fluid_tilde_dsp, gensym("dsp"), A_CANT, 0);
porres's avatar
porres committed
357
    class_addfloat(fluid_tilde_class, (t_method)fluid_float); // raw midi input
porres's avatar
porres committed
358
    class_addmethod(fluid_tilde_class, (t_method)fluid_load, gensym("load"), A_GIMME, 0);
porres's avatar
gen    
porres committed
359
    class_addmethod(fluid_tilde_class, (t_method)fluid_gen, gensym("gen"), A_GIMME, 0);
porres's avatar
porres committed
360
    class_addmethod(fluid_tilde_class, (t_method)fluid_bank, gensym("bank"), A_GIMME, 0);
porres's avatar
porres committed
361
    class_addmethod(fluid_tilde_class, (t_method)fluid_note, gensym("note"), A_GIMME, 0);
porres's avatar
porres committed
362
363
364
    class_addlist(fluid_tilde_class, (t_method)fluid_note); // same as note
    class_addmethod(fluid_tilde_class, (t_method)fluid_control_change, gensym("ctl"), A_GIMME, 0);
    class_addmethod(fluid_tilde_class, (t_method)fluid_program_change, gensym("pgm"), A_GIMME, 0);
porres's avatar
porres committed
365
    class_addmethod(fluid_tilde_class, (t_method)fluid_polytouch, gensym("polytouch"), A_GIMME, 0);
porres's avatar
gen    
porres committed
366
    class_addmethod(fluid_tilde_class, (t_method)fluid_aftertouch, gensym("touch"), A_GIMME, 0);
porres's avatar
porres committed
367
    class_addmethod(fluid_tilde_class, (t_method)fluid_pitch_bend, gensym("bend"), A_GIMME, 0);
porres's avatar
porres committed
368
    class_addmethod(fluid_tilde_class, (t_method)fluid_sysex, gensym("sysex"), A_GIMME, 0);
porres's avatar
porres committed
369
    class_addmethod(fluid_tilde_class, (t_method)fluid_info, gensym("info"), 0);
porres's avatar
porres committed
370
}
porres's avatar
porres committed
371
372

//  fluid_synth_system_reset(synth_); // panic