Skip to content

Commit b1e363f

Browse files
committed
- Added sequencer from binary 'waveform' and ADSR data.
- Added waveform generic API to create waveforms, and use period instead of frequency in waveform (avoid division on MCUs) - Introduced 'def' struct in ADSR API for reuse - Multi-track MML parser and compilation - Added some samples from https://electronicmusic.fandom.com/wiki/Music_Macro_Language - Added scale.mml for testing
1 parent c4a67b9 commit b1e363f

19 files changed

+1294
-85
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ sine.c
77
obj
88
bin
99
out.wav
10+
sequencer.bin

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ $(BINDIR)/synth: $(OBJECTS) $(OBJDIR)/poly.a
2424
@[ -d $(BINDIR) ] || mkdir -p $(BINDIR)
2525
$(CC) -g -o $@ $(LDFLAGS) $(LIBS) $^
2626

27-
$(OBJDIR)/poly.a: $(OBJDIR)/adsr.o $(OBJDIR)/waveform.o
27+
$(OBJDIR)/poly.a: $(OBJDIR)/adsr.o $(OBJDIR)/waveform.o $(OBJDIR)/mml.o $(OBJDIR)/sequencer.o
2828
$(AR) rcs $@ $^
2929

3030
$(OBJDIR)/%.o: $(SRCDIR)/%.c

README.md

+128
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,125 @@ help to periodically seed it, perhaps by taking the least-significant
204204
bits of ADC readings and feeding those into `srand` to give it some true
205205
randomness.
206206

207+
## Sequencer
208+
209+
Since the synthesizer state machine is effective in defining when a "note" envelope is terminated, it is then possible to store all the subsequent "notes" in a stream of consecutive *steps*. Each step contains a pair of waveform settings and ADSR settings.
210+
211+
This allow polyphonic tunes to be "pre-compiled" and stored in small binary files, or microcontroller EEPROM, and to be accessed in serial fashion.
212+
213+
Each tune are stored in a way that each frame in the stream should feed the next available channel with the `enable` flag of the `struct poly_synth_t` structure reset.
214+
215+
In order to arrange the steps of all the channels in the correct sequence, a *sequencer compiler* has to be run on all the channel steps, and sort it correctly using an instance of the synth configured in the exact way of the target system (e.g. same sampling rate, same number of voices, etc...).
216+
217+
This compiler is not optimized to run on a microcontroller (it requires dynamic memory allocation), but to be run on a PC in order to obtain compact binary files to be played by the sequencer on the host MCU.
218+
219+
To save memory for the tiniest 8-bit microcontrollers, the sequencer stream header and the steps are defined in a compact 8-bit binary format:
220+
221+
```
222+
// A frame
223+
struct seq_frame_t {
224+
/*! Envelope definition */
225+
struct adsr_env_def_t adsr_def;
226+
/*! Waveform definition */
227+
struct voice_wf_def_t waveform_def;
228+
};
229+
```
230+
231+
where `adsr_env_def_t` is the argument for the `adsr_config`, and `voice_wf_def_t` is the minimum set of arguments to initialize a waveform.
232+
233+
In order to save computational-demanding 16-bit division operations on 8-bit targets, the waveform frequency in the definition is expressed as waveform period instead of frequency in Hz, to allow faster play at runtime.
234+
235+
This requires the sequencer compiler to known in advance the target sampling rate.
236+
237+
For this reason, a stream header contains the information to avoid issues during reproduction:
238+
239+
```
240+
struct seq_stream_header_t {
241+
/*! Sampling frequency required for correct timing */
242+
uint16_t synth_frequency;
243+
/*! Size of a single frame in bytes */
244+
uint8_t frame_size;
245+
/*! Number of voices */
246+
uint8_t voices;
247+
/*! Total frame count */
248+
uint16_t frames;
249+
/*! Follow frames data, as stream of seq_frame_t */
250+
};
251+
```
252+
253+
The `frame_size` field is useful when the code in the target microcontroller is compiled with different setting (e.g. different time scale, or different set of features that requires less data, like no Attack/Decay, etc...).
254+
255+
### Typical usage
256+
257+
The sequencer can be fed via a callback, in order to support serial read for example from serial EEPROM or streams.
258+
259+
```c
260+
/*! Requires a new frame. The handler must return 1 if a new frame was acquired, or zero if EOF */
261+
void seq_set_stream_require_handler(uint8_t (*handler)(struct seq_frame_t* frame));
262+
263+
/*!
264+
* Plays a stream sequence of frames, in the order requested by the synth.
265+
* The frames must then be sorted in the same fetch order and not in channel order.
266+
*/
267+
int seq_play_stream(const struct seq_stream_header_t* stream_header, uint8_t voice_count, struct poly_synth_t* synth);
268+
269+
/*! Use it when `seq_play_stream` is in use, one call per sample */
270+
void seq_feed_synth(struct poly_synth_t* synth);
271+
```
272+
273+
## MML compiler
274+
275+
A very common language to define tunes in a quasi-human-readable fashion is the [Music Macro Language](https://en.wikipedia.org/wiki/Music_Macro_Language) (MML).
276+
277+
The project contains an implementation of a MML parser that creates a sequencer stream. In that way, it is possible to 'compile' tunes into binary streams, embed it in the microcontroller and play it from the sequencer stream with the least as computational power as possible.
278+
279+
The MML dialect implemented supports multi-voice: each voice can be specified on a different line, prefixed with the voice number (from *A* to *Z*).
280+
281+
| command | meaning |
282+
| ------------- |-------------|
283+
| `cdefgab` | The letters `a` to `g` correspond to the musical pitches and cause the corresponding note to be played. Sharp notes are produced by appending a `+` or `#`, and flat notes by appending a `-`. The length of a note can be specified by appending a number representing its length (see `l` command). One or more dots `.` can be added to increase the length of 3/2. |
284+
| `p` or `r` | A pause or rest. Like the notes, it is possible to specify the length appending a number and/or dots. |
285+
| `n`\<n> | Plays a *note code*, between 0 and 84. `0` is the C at octave 0, `33` is A at octave 2 (440Hz), etc... |
286+
| `o`\<n\> | Specify the octave the instrument will play in (from 0 to 6). The default octave is 2 (corresponding to the fourth-octave in scientific pitch).
287+
| `<`, `>` | Used to step up or down one octave.
288+
| `l`\<n\> | Specify the default length used by notes or rests which do not explicitly define one. `4` means 1/4, `16` means 1/16 etc... One or more dots `.` can be added to increase the length of 3/2.
289+
| `v`\<n\> | Sets the volume of the instruments. It will set the current waveform amplitude (127 being the maximum modulation).
290+
| `t`\<n\> | Sets the tempo in beats per minute.
291+
| `mn`, `ml`, `ms` | Sets the articulation for the current instrument. Stands for *music normal* (note plays for 7/8 of the length), *music legato* (note plays full length) and *music staccato* (note plays 3/4 of length). This is implemented using the *decay* of ADSR modulation.
292+
| `ws`, `ww`, `wt` (*) | Sets the square waveform, sawtooth waveform or triangle waveform for the current instrument.
293+
| `\|` | The pipe character, used in music sheet notation to help aligning different channel, is ignored.
294+
| `#`, `;` | Characters to denote comment lines: it will skip the rest of the line.
295+
| `A-Z` (*) | Sets the active voice for the current MML line. Multiple characters can be specified: in that case all the selected voices will receive the MML commands until the end of the line.
296+
297+
(*) custom MML dialect.
298+
299+
The MML compiler is not optimized to run on a microcontroller (it requires dynamic memory allocation), but to be run on a PC in order to obtain the data to create a binary stream for the sequencer. The typical usage is a compiler for PC.
300+
301+
### Typical usage
302+
303+
The MML file should be loaded entirely in memory to be compiled.
304+
305+
```c
306+
// Set the error handler in order to show errors and line/col counts
307+
mml_set_error_handler(stderr_err_handler);
308+
struct seq_frame_map_t map;
309+
// Parse the MML file and produce sequencer frames as stream.
310+
if (mml_compile(mml_content, &map)) {
311+
// Error
312+
}
313+
// Compile the channel data map in a stream
314+
struct seq_frame_t* frame_stream;
315+
int frame_count;
316+
int voice_count;
317+
seq_compile(&map, &frame_stream, &frame_count, &voice_count);
318+
319+
// Save the frame stream...
320+
321+
// Free memory
322+
mml_free(map);
323+
seq_free(frame_stream);
324+
```
325+
207326
Ports
208327
-----
209328

@@ -329,3 +448,12 @@ Once the `enable` bit-mask is set, the program loops, playing sound via
329448
all bits in the `enable` bit-mask are cleared by the ADSR state machines.
330449

331450
When the program runs out of command line arguments, or the script ends, it exits.
451+
452+
In addition, the PC port can be used to compile MML tunes to the sequencer binary format:
453+
454+
* `compile-mml FILE.mml` compiles the .mml file and produces a `sequencer.bin` output
455+
456+
and to play sequencer files as well:
457+
458+
* `sequencer FILE.bin` loads and plays the sequencer binary file passed as input.
459+

adsr.c

+28-28
Original file line numberDiff line numberDiff line change
@@ -95,23 +95,23 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
9595
adsr->sustain_amp);
9696

9797
/* Are registers set up? */
98-
if (!adsr->time_scale)
98+
if (!adsr->def.time_scale)
9999
return 0;
100100
_DPRINTF("adsr=%p time scale set\n", adsr);
101101

102-
if (!(adsr->delay_time || adsr->attack_time
103-
|| adsr->decay_time
104-
|| adsr->sustain_time
105-
|| adsr->release_time))
102+
if (!(adsr->def.delay_time || adsr->def.attack_time
103+
|| adsr->def.decay_time
104+
|| adsr->def.sustain_time
105+
|| adsr->def.release_time))
106106
return 0;
107107
_DPRINTF("adsr=%p envelope timings set\n", adsr);
108108

109-
if (!(adsr->peak_amp || adsr->sustain_amp))
109+
if (!(adsr->def.peak_amp || adsr->def.sustain_amp))
110110
return 0;
111111
_DPRINTF("adsr=%p envelope amplitudes set\n", adsr);
112112

113113
/* All good */
114-
if (adsr->delay_time)
114+
if (adsr->def.delay_time)
115115
adsr->state = ADSR_STATE_DELAY_INIT;
116116
else
117117
adsr->state = ADSR_STATE_DELAY_EXPIRE;
@@ -123,7 +123,7 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
123123
/* Setting up a delay */
124124
adsr->amplitude = 0;
125125
adsr->next_event = adsr_num_samples(
126-
adsr->time_scale, adsr->delay_time);
126+
adsr->def.time_scale, adsr->def.delay_time);
127127
adsr->state = ADSR_STATE_DELAY_EXPIRE;
128128
/* Wait for delay */
129129
return adsr->amplitude;
@@ -133,16 +133,16 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
133133
_DPRINTF("adsr=%p DELAY EXPIRE\n", adsr);
134134

135135
/* Delay has expired */
136-
if (adsr->attack_time)
136+
if (adsr->def.attack_time)
137137
adsr->state = ADSR_STATE_ATTACK_INIT;
138138
else
139139
adsr->state = ADSR_STATE_ATTACK_EXPIRE;
140140
}
141141

142142
if (adsr->state == ADSR_STATE_ATTACK_INIT) {
143143
/* Attack is divided into 16 segments */
144-
adsr->time_step = (uint16_t)((adsr->attack_time
145-
* adsr->time_scale) >> 4);
144+
adsr->time_step = (uint16_t)((adsr->def.attack_time
145+
* adsr->def.time_scale) >> 4);
146146
adsr->counter = 16;
147147
adsr->next_event = adsr->time_step;
148148
adsr->state = ADSR_STATE_ATTACK;
@@ -158,9 +158,9 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
158158
if (adsr->counter) {
159159
/* Change of amplitude */
160160
uint16_t lin_amp = (16-adsr->counter)
161-
* adsr->peak_amp;
161+
* adsr->def.peak_amp;
162162
uint16_t exp_amp = adsr_attack_amp(
163-
adsr->peak_amp, adsr->counter);
163+
adsr->def.peak_amp, adsr->counter);
164164
lin_amp >>= ADSR_LIN_AMP_FACTOR;
165165
_DPRINTF("adsr=%p ATTACK lin=%d exp=%d\n",
166166
adsr, lin_amp, exp_amp);
@@ -177,7 +177,7 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
177177
if (adsr->state == ADSR_STATE_ATTACK_EXPIRE) {
178178
_DPRINTF("adsr=%p ATTACK EXPIRE\n", adsr);
179179

180-
if (adsr->decay_time)
180+
if (adsr->def.decay_time)
181181
adsr->state = ADSR_STATE_DECAY_INIT;
182182
else
183183
adsr->state = ADSR_STATE_DECAY_EXPIRE;
@@ -187,10 +187,10 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
187187
_DPRINTF("adsr=%p DECAY INIT\n", adsr);
188188

189189
/* We should be at full amplitude */
190-
adsr->amplitude = adsr->peak_amp;
190+
adsr->amplitude = adsr->def.peak_amp;
191191

192-
adsr->time_step = (uint16_t)((adsr->decay_time
193-
* adsr->time_scale) >> 4);
192+
adsr->time_step = (uint16_t)((adsr->def.decay_time
193+
* adsr->def.time_scale) >> 4);
194194
adsr->counter = 16;
195195
adsr->next_event = adsr->time_step;
196196
adsr->state = ADSR_STATE_DECAY;
@@ -201,12 +201,12 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
201201

202202
if (adsr->counter) {
203203
/* Linear decrease in amplitude */
204-
uint16_t delta = adsr->peak_amp
205-
- adsr->sustain_amp;
204+
uint16_t delta = adsr->def.peak_amp
205+
- adsr->def.sustain_amp;
206206
delta *= adsr->counter;
207207
delta >>= 4;
208208

209-
adsr->amplitude = adsr->sustain_amp + delta;
209+
adsr->amplitude = adsr->def.sustain_amp + delta;
210210
adsr->next_event = adsr->time_step;
211211
adsr->counter--;
212212
} else {
@@ -217,7 +217,7 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
217217
if (adsr->state == ADSR_STATE_DECAY_EXPIRE) {
218218
_DPRINTF("adsr=%p DECAY EXPIRE\n", adsr);
219219

220-
if (adsr->sustain_time)
220+
if (adsr->def.sustain_time)
221221
adsr->state = ADSR_STATE_SUSTAIN_INIT;
222222
else
223223
adsr->state = ADSR_STATE_SUSTAIN_EXPIRE;
@@ -226,9 +226,9 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
226226
if (adsr->state == ADSR_STATE_SUSTAIN_INIT) {
227227
_DPRINTF("adsr=%p SUSTAIN INIT\n", adsr);
228228

229-
adsr->amplitude = adsr->sustain_amp;
229+
adsr->amplitude = adsr->def.sustain_amp;
230230
adsr->next_event = adsr_num_samples(
231-
adsr->time_scale, adsr->sustain_time);
231+
adsr->def.time_scale, adsr->def.sustain_time);
232232
adsr->state = ADSR_STATE_SUSTAIN_EXPIRE;
233233
/* Wait for delay */
234234
return adsr->amplitude;
@@ -237,7 +237,7 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
237237
if (adsr->state == ADSR_STATE_SUSTAIN_EXPIRE) {
238238
_DPRINTF("adsr=%p SUSTAIN EXPIRE\n", adsr);
239239

240-
if (adsr->release_time)
240+
if (adsr->def.release_time)
241241
adsr->state = ADSR_STATE_RELEASE_INIT;
242242
else
243243
adsr->state = ADSR_STATE_RELEASE_EXPIRE;
@@ -246,8 +246,8 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
246246
if (adsr->state == ADSR_STATE_RELEASE_INIT) {
247247
_DPRINTF("adsr=%p RELEASE INIT\n", adsr);
248248

249-
adsr->time_step = (uint16_t)((adsr->release_time
250-
* adsr->time_scale) >> 4);
249+
adsr->time_step = (uint16_t)((adsr->def.release_time
250+
* adsr->def.time_scale) >> 4);
251251
adsr->counter = 16;
252252
adsr->next_event = adsr->time_step;
253253
adsr->state = ADSR_STATE_RELEASE;
@@ -259,9 +259,9 @@ uint8_t adsr_next(struct adsr_env_gen_t* const adsr) {
259259
if (adsr->counter) {
260260
/* Change of amplitude */
261261
uint16_t lin_amp = adsr->counter
262-
* adsr->sustain_amp;
262+
* adsr->def.sustain_amp;
263263
uint16_t exp_amp = adsr_release_amp(
264-
adsr->sustain_amp, adsr->counter);
264+
adsr->def.sustain_amp, adsr->counter);
265265
lin_amp >>= ADSR_LIN_AMP_FACTOR;
266266
_DPRINTF("adsr=%p RELEASE lin=%d exp=%d\n",
267267
adsr, lin_amp, exp_amp);

adsr.h

+18-23
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,9 @@
4747
#define ADSR_INFINITE UINT8_MAX
4848

4949
/*!
50-
* ADSR Envelope Generator data. 20 bytes.
50+
* ADSR Envelope Generator definition. 11 bytes.
5151
*/
52-
struct adsr_env_gen_t {
53-
/*! Time to next event, samples. UINT32_MAX = infinite */
54-
uint32_t next_event;
55-
/*! Time step, samples */
56-
uint16_t time_step;
52+
struct adsr_env_def_t {
5753
/*! Time scale, samples per unit */
5854
uint32_t time_scale;
5955
/*! Delay period, time units. UINT8_MAX = infinite */
@@ -70,12 +66,24 @@ struct adsr_env_gen_t {
7066
uint8_t peak_amp;
7167
/*! Sustain amplitude */
7268
uint8_t sustain_amp;
73-
/*! Present amplitude */
74-
uint8_t amplitude;
69+
};
70+
71+
/*!
72+
* ADSR Envelope Generator data. 20 bytes.
73+
*/
74+
struct adsr_env_gen_t {
75+
/*! Definition */
76+
struct adsr_env_def_t def;
77+
/*! Time to next event, samples. UINT32_MAX = infinite */
78+
uint32_t next_event;
79+
/*! Time step, samples */
80+
uint16_t time_step;
7581
/*! ADSR state */
7682
uint8_t state;
7783
/*! ADSR counter */
7884
uint8_t counter;
85+
/*! Present amplitude */
86+
uint8_t amplitude;
7987
};
8088

8189
/*!
@@ -105,21 +113,8 @@ static inline void adsr_reset(struct adsr_env_gen_t* const adsr) {
105113
/*!
106114
* Configure the ADSR.
107115
*/
108-
static inline void adsr_config(struct adsr_env_gen_t* const adsr,
109-
uint32_t time_scale, uint8_t delay_time,
110-
uint8_t attack_time, uint8_t decay_time,
111-
uint8_t sustain_time, uint8_t release_time,
112-
uint8_t peak_amp, uint8_t sustain_amp) {
113-
114-
adsr->time_scale = time_scale;
115-
adsr->delay_time = delay_time;
116-
adsr->attack_time = attack_time;
117-
adsr->decay_time = decay_time;
118-
adsr->sustain_time = sustain_time;
119-
adsr->release_time = release_time;
120-
adsr->peak_amp = peak_amp;
121-
adsr->sustain_amp = sustain_amp;
122-
116+
static inline void adsr_config(struct adsr_env_gen_t* const adsr, struct adsr_env_def_t* const def) {
117+
adsr->def = *def;
123118
adsr_reset(adsr);
124119
}
125120

0 commit comments

Comments
 (0)