1 
2 /**
3  * A simple FM synthesizer with Jack.
4  * It is a port of the miniFMsynth example from ALSA.
5  */
6 
7 module miniFMsynth;
8 import jack.client;
9 import jack.midiport;
10 import std.stdio;
11 import std.math;
12 import core.stdc.string;
13 
14 void main()
15 {
16   JackClient client = new JackClient;
17   client.open("miniFMsynth", JackOptions.JackNoStartServer, null);
18   scope(exit) client.close();
19 
20   writeln("New jack_client with name: " ~ client.get_name());
21 
22   JackPort midi = client.register_port("In", JACK_DEFAULT_MIDI_TYPE, JackPortFlags.JackPortIsInput, 0);
23   JackPort out1 = client.register_port("Out1", JACK_DEFAULT_AUDIO_TYPE, JackPortFlags.JackPortIsOutput, 0);
24   JackPort out2 = client.register_port("Out2", JACK_DEFAULT_AUDIO_TYPE, JackPortFlags.JackPortIsOutput, 0);
25 
26   auto fm = new SynthFM;
27   fm.srate = client.get_sample_rate();
28   fm.buf = new short[client.get_buffer_size()];
29 
30   fm.modulation = 7.8;
31   fm.harmonic = 3;
32   fm.subharmonic = 5;
33   fm.transpose = 24;
34   fm.attack = 0.01;
35   fm.decay = 0.8;
36   fm.sustain = 0.0;
37   fm.release = 0.1;
38   fm.pitch = 0.0;
39 
40   client.process_callback = delegate int(jack_nframes_t nframes) {
41     JackMidiPortBuffer midibuf = midi.get_midi_buffer(nframes);
42     foreach (JackMidiEvent event; midibuf.iter_events()) {
43       if (event.size == 3) {
44         if (event.buffer[0] == 0x80 ||
45             event.buffer[0] == 0x90 && event.buffer[2] == 0)
46           fm.noteoff(event.buffer[1], event.buffer[2]);
47         else if (event.buffer[0] == 0x90)
48           fm.noteon(event.buffer[1], event.buffer[2]);
49       }
50     }
51 
52     float *buf1 = out1.get_audio_buffer(nframes);
53     float *buf2 = out2.get_audio_buffer(nframes);
54 
55     fm.compute(nframes);
56 
57     for (jack_nframes_t i = 0; i < nframes; ++i) {
58       buf1[i] = cast(double)fm.buf[i] / short.max;
59     }
60     buf2[0..nframes] = buf1[0..nframes];
61 
62     return 0;
63   };
64 
65   client.activate();
66 
67   writeln("Press a key to stop.");
68   stdin.readln();
69 }
70 
71 class SynthFM
72 {
73   double srate;
74 
75   enum POLY = 10;
76   enum GAIN = 5000.0;
77 
78   short[] buf;
79   double pitch, modulation, attack, decay, sustain, release;
80   double[POLY] phi, phi_mod, velocity, env_time, env_level;
81   int harmonic, subharmonic, transpose, rate;
82   int[POLY] note, gate, note_active;
83 
84   this() {
85     for (uint i = 0; i < POLY; ++i) {
86       phi[i] = 0.0;
87       phi_mod[i] = 0.0;
88       velocity[i] = 0.0;
89       env_time[i] = 0.0;
90       env_level[i] = 0.0;
91     }
92   }
93 
94   void noteon(int note, int vel)
95   {
96     for (uint l1 = 0; l1 < POLY; l1++) {
97       if (! note_active[l1]) {
98         this.note[l1] = note;
99         velocity[l1] = vel / 127.0;
100         env_time[l1] = 0;
101         gate[l1] = 1;
102         note_active[l1] = 1;
103         break;
104       }
105     }
106   }
107 
108   void noteoff(int note, int vel)
109   {
110     for (uint l1 = 0; l1 < POLY; l1++) {
111       if (gate[l1] && note_active[l1] && (this.note[l1] == note)) {
112         env_time[l1] = 0;
113         gate[l1] = 0;
114       }
115     }
116   }
117 
118   static double envelope(int *note_active, int gate, double *env_level, double t, double attack, double decay, double sustain, double release)
119   {
120     if (gate)  {
121       if (t > attack + decay) return(*env_level = sustain);
122       if (t > attack) return(*env_level = 1.0 - (1.0 - sustain) * (t - attack) / decay);
123       return(*env_level = t / attack);
124     } else {
125       if (t > release) {
126         if (note_active) *note_active = 0;
127         return(*env_level = 0);
128       }
129       return(*env_level * (1.0 - t / release));
130     }
131   }
132 
133   void compute(uint nframes)
134   {
135     short *buf = this.buf.ptr;
136 
137     memset(buf, 0, nframes * float.sizeof);
138     for (int l2 = 0; l2 < POLY; l2++) {
139       if (note_active[l2]) {
140         double f1 = 8.176 * exp(cast(double)(transpose+note[l2]-2)*log(2.0)/12.0);
141         double f2 = 8.176 * exp(cast(double)(transpose+note[l2])*log(2.0)/12.0);
142         double f3 = 8.176 * exp(cast(double)(transpose+note[l2]+2)*log(2.0)/12.0);
143         double freq_note = (pitch > 0) ? f2 + (f3-f2)*pitch : f2 + (f2-f1)*pitch;
144         double dphi = PI * freq_note / (srate / 2.0);
145         double dphi_mod = dphi * cast(double)harmonic / cast(double)subharmonic;
146         for (int l1 = 0; l1 < nframes; l1++) {
147           phi[l2] += dphi;
148           phi_mod[l2] += dphi_mod;
149           if (phi[l2] > 2.0 * PI) phi[l2] -= 2.0 * PI;
150           if (phi_mod[l2] > 2.0 * PI) phi_mod[l2] -= 2.0 * PI;
151           double env = envelope(&note_active[l2], gate[l2], &env_level[l2], env_time[l2], attack, decay, sustain, release);
152           double sound = GAIN * env
153             * velocity[l2] * sin(phi[l2] + modulation * sin(phi_mod[l2]));
154           env_time[l2] += 1.0 / srate;
155           buf[l1] += sound;
156         }
157       }
158     }
159   }
160 }