synth_music/lib.rs
1/*!
2A framework-like crate to compose and synthetisize music. It's possible to work
3without external tools just using this crate. There are provided tools for
4composing music, synthetisizing sounds, and exporting the results into a file.
5
6# General principles
7
8Most important key information on how this crate is structured.
9
10## Terminology
11
12A whole music piece that would usually reside in a dedicated sound file is
13called a **composition**.
14
15A composition is made of several **sections** that are put behind after one
16another. A section has a defined speed (BPM) and Music Key. These parameters
17can change with every new section.
18
19A section contains several **tracks**. Unlike sections, all tracks of a section
20are played at the same time. The track stores the individual notes and is
21assigned an **instrument**. Tracks are also designed to be reusable.
22
23An instrument is a user-defined implementation to generate sound given a note or
24frequency. Here the sound synthesis happens. There are predefined instruments
25included, but it's encouraged to implement own instruments.
26
27## Modularity
28
29A lot of things are implemented using exposed traits. There are default
30implementations included. In some cases it's encouraged for the user to
31implement their own functions (like with instruments), in other cases an
32alternative implementation can be done when necessary.
33
34### Custom note systems
35
36It's possible to implement custom note systems. An implementation for the
37standard 12-TET note system is already provided, which will serve most uses.
38The most related alternative would be the 24-TET note system, which the user
39has to implement themself, if they want to use it.
40
41A common non-standard note system is e.g. actions for a drumset. Here it doesn't
42make much sense to use notes, but rather actions. The user needs to implement
43this system for themself. Check the "custom_note_system" example for details.
44
45### Custom track handling
46
47There are a few provided implementations for placing notes on a Track. If these
48do not satisfy the needs of the user, they can implement a custom version of
49the Track.
50
51### Custom exporter
52
53There's a provided implementation for WAV files. The user can implement
54exporting to other file formats such as mp3, ogg, etc...
55
56# Usage
57
58## Placing notes on a track
59
60It's best to always put this in a dedicated function, as we will make use of
61`use` in local scope. This way tracks can also be used across different
62instruments.
63
64```rust
65use synth_music::prelude::*;
66
67fn main() {
68
69 let instrument = predefined::SineGenerator;
70
71 // Somewhere in main
72
73 let track = track_example(instrument);
74}
75
76fn track_example<T>(instrument: T) -> UnboundTrack<TET12ScaledTone, T>
77where
78 T: Instrument<ConcreteValue = TET12ConcreteTone>
79{
80 use tet12::*; // Note values
81 use length::*; // Note lengths
82
83 let mut track = UnboundTrack::new(instrument);
84 track.set_intensity(0.7);
85
86 // Placing notes regularly
87 track.note(QUARTER, first(4));
88 track.note(HALF, second(4));
89 track.note(QUARTER.dot(), third(3)); // .dot() on length will extend it by half
90 track.note(QUARTER, fifth(3).sharp()); // .sharp() on height will increment by one semitone
91
92 // Placing notes quickly via macro
93 sequential_notes!(track, EIGTH,
94 first(4),
95 seventh(3).sharp(),
96 sixth(3),
97 fifth(3),
98 fourth(3),
99 third(3),
100 second(3),
101 first(3)
102 );
103
104 // Placing multiple notes on top of each other
105 // this is a chord
106 notes!(track, HALF,
107 first(3),
108 third(3),
109 fifth(3)
110 );
111
112 // Simple logic can also be applied
113 for i in 1..5 {
114 track.note(QUARTER, first(i));
115 }
116
117 return track;
118}
119```
120
121As you can see, there are several ways to place notes. The most versatile way
122is to place them individually, but it leads to a lot of repetitions, because
123"track.note(" has to be called for every note.
124
125The macros provide a way to eliminate a lot of unnecessary repetition and save
126a lot of time for writing music. It's also possible to use logic or even
127branching, which is unique to this style of composing.
128
129## Reusing specific segments
130
131We have the ability to place notes using functions. In traditional composition,
132it's only possible to repeat sections of the whole piece. The function approach
133is much more versatile, as it's possible to repeat only specific tracks or even
134parts of tracks. This is especially useful for simple melodies like the bass.
135Below is an example to demonstrate that.
136
137```rust
138use synth_music::prelude::*;
139
140use tet12::*;
141use length::*;
142
143let mut track = UnboundTrack::new(predefined::SineGenerator);
144
145// Some melody here
146
147// Reused note segment
148apply_chord(&mut track);
149
150// Some melody there
151
152// ...
153
154fn apply_chord<T, U>(track: &mut T)
155where
156 T: MusicTrack<TET12ScaledTone, U>,
157 U: Instrument<ConcreteValue = TET12ConcreteTone>,
158{
159 use tet12::*;
160 use length::*;
161
162 notes!(track, HALF,
163 first(3),
164 third(3),
165 fifth(3)
166 );
167
168 notes!(track, HALF,
169 third(3),
170 fifth(3),
171 first(4)
172 );
173}
174
175```
176
177It's possible to work on mutable references of Tracks instead of returning a new
178one. There are probably even more ways to do other things, so get creative with
179what you got.
180
181## Dynamics
182
183Dynamics in music refer to the loudness or intensity at which should be played.
184These dynamics can change dynamically (no pun intended) throughout the song.
185
186```rust
187use synth_music::prelude::*;
188use tet12::*;
189use length::*;
190
191let mut track = UnboundTrack::new(predefined::SineGenerator);
192
193// Set the intensity for new notes to be 70% of maximum volume
194track.set_intensity(0.7);
195
196track.note(HALF, first(3)); // Intensity = 0.7
197track.note(HALF, first(3)); // Intensity = 0.7
198
199track.set_intensity(0.2);
200
201track.note(HALF, first(3)); // Intensity = 0.2
202
203
204// Track will start changing intensity arriving at value specified later
205track.start_dynamic_change();
206track.note(QUARTER, third(3));
207track.note(QUARTER, fourth(3));
208track.note(QUARTER, fifth(3));
209track.note(QUARTER, first(3));
210// Marks the end of the dynamic change, the passed value is the target intensity.
211// This target intensity is now the actual intensity of the track.
212track.end_dynamic_change(0.6);
213```
214
215Calling `track.set_intensity(x)` will change the intensity of the notes placed
216afterwards. This is equivalent to a dynamics marker in traditional music
217notation (e.g. "f" for "forte" or "loud"; "p" for "piano" or "quiet").
218
219The "dynamic change" will smoothly transition the intensity from the currently
220set intensity of the track to the value specified at "end_dynamic_change". In
221traditional music this is called "crescendo" for becoming louder or
222"decrescendo" for becoming quieter. This crate does not differentiate becoming
223louder or becoming quieter.
224
225## Implementing instruments
226
227Instruments represent the entire sound synthesis part of this crate. Here, most
228implementation is left to the user, the crate will only provide useful info for
229generating the sound, but the actual sound synthesis is the responsibility of
230the user. There is an exposed trait `Instrument` that needs to be implemented.
231
232All trait functions of `Instrument` already have a default implementation. This
233is to avoid repeating the same implementation, since synthesis for almost all
234instruments works the same. If you do not implement any functions, the
235instrument will only render silence.
236
237Before going to an example, a brief explanation on all trait functions sorted
238after how likely you're going to need to override them:
239
240- `render_sample` - Render a sample of a tone at a given time. Use this if the
241samples can be computed independent of each other (given the time). The
242amplitude of the tone should always be at `1.0`.
243
244- `get_intensity` - Return the intensity at a given time. Override if you want
245the intensity e.g. to become quieter with time.
246
247- `get_num_samples` - Return the amount of samples the buffer should consist of
248in total.
249
250- `post_process` - Called after everything else with write access to the buffer.
251
252- `render_tone_buffer` - Wraps `render_sample`, override if you need to render
253the whole buffer for a single tone in a single function. `render_sample` will
254become useless (will never be called) if you override this and not call it
255yourself.
256
257- `apply_intensity` - Wraps `get_intensity` the same way as `render_tone_buffer`
258does with `render_sample`.
259
260- `render` - This function is called by the crate during the rendering stage,
261and it calls all other functions above for rendering. Only overwrite if you want
262to have absolute control over the render. If you do override this function,
263every other function here will become useless (not called) if you do not do so
264yourself.
265
266Look for the `Instrument` documentation for more details
267
268The buffer works with f32 samples, where 1.0 or -1.0 are the maximum amplitude,
269which should generally not be exceeded. The output buffer can be shorter or
270longer than expected, it will then automatically get extended or mixed with the
271following tones.
272
273Now, on to the example:
274
275```rust
276use synth_music::prelude::*;
277use tet12::TET12ConcreteTone;
278use std::time::Duration;
279
280#[derive(Clone, Copy)]
281struct ExampleInstrument {
282 count: u32,
283 decay_speed: f32,
284}
285
286impl ExampleInstrument {
287 pub fn new(count: u32, decay_speed: f32) -> Self {
288 Self {
289 count,
290 decay_speed,
291 }
292 }
293
294 // Wave function (sine)
295 fn wave(frequency: f64, time: Duration) -> f32 {
296 use std::f64::consts::PI;
297 (time.as_secs_f64() * frequency * 2.0 * PI).sin() as f32
298 }
299
300 // Exponential decay over time
301 fn decay_factor(&self, time: Duration) -> f32 {
302 0.5_f32.powf(time.as_secs_f32() * self.decay_speed)
303 }
304}
305
306impl Instrument for ExampleInstrument {
307 // Specify that this instrument operates on 12-TET notes
308 type ConcreteValue = TET12ConcreteTone;
309
310 // The sample consists of several harmonic frequencies.
311 fn render_sample(&self, tone: Self::ConcreteValue, time: Duration) -> f32 {
312 let frequency = tone.to_frequency() as f64;
313 let mut sample = 0.0;
314
315 for n in 0..self.count {
316 let factor = (2 * n + 1) as f64;
317 sample += Self::wave(frequency * factor, time);
318 }
319
320 return sample;
321 }
322
323 // The intensity will become exponentially quieter with time
324 fn get_intensity(&self, tones: &Tone<Self::ConcreteValue>, time: Duration) -> f32 {
325 let base = tones.intensity.start;
326 let factor = self.decay_factor(time);
327
328 return base * factor;
329 }
330}
331```
332
333This is a simple instrument that has a variable amount of harmonics in its base
334sine-wave tone. These harmonics don't get quieter with higher frequencies, so
335the sound is harsh and loud, especially with many harmonics.
336
337This is the most simple use case, which you'll need 90% of the time when the
338instrument is based on predictable waves. If you needed access to the whole
339buffer while rendering for some reason, you would need to implement
340`render_tone_buffer`, etc..
341
342For more examples please look into the examples folder.
343
344## Exporting
345
346Exporting is the last step of making music. In this stage all the instrument
347implementations will actually be called and the tracks will be rendered and
348mixed into sections, which will then be combined into the final composition and
349written to a file.
350
351There are two types of information that will be important to define here. There
352are the **settings** which apply for the whole composition, and **SectionInfo**,
353which apply only to one section. The settings include technical values like
354sample rate, and SectionInfo is info that can change during a composition, like
355the speed in BPM or the music key.
356
357Unfortunately, due to the Tracks being highly generic Traits, it's currently not
358possible to store several tracks together (e.g. in a Vec) without forcing their
359types to be the absolute same. To combat this, sections are actually just
360represented by a macro call and directly rendered into a Buffer. Another macro
361call can then piece these buffers together into a composition.
362
363```no_run
364use synth_music::prelude::*;
365
366// Define settings
367let settings = CompositionSettings {
368 sample_rate: 44100,
369};
370
371// Info for beginning. Info always contains a reference to the settings.
372let info_begin = SectionInfo {
373 bpm: 120.0,
374 key: MusicKey {
375 tonic: KeyTonic::A,
376 key_type: KeyType::Minor,
377 },
378
379 settings: &settings,
380};
381
382let info_end = SectionInfo {
383 bpm: 140.0,
384 key: MusicKey {
385 tonic: KeyTonic::Asharp,
386 key_type: KeyType::Minor,
387 },
388
389 settings: &settings,
390};
391
392// Any instruments work, different ones can be used for different tracks
393let instrument = predefined::SineGenerator;
394
395let track_begin_melody = track_begin_melody(instrument);
396let track_begin_bass = track_begin_bass(instrument);
397
398let track_end_melody = track_end_melody(instrument);
399let track_end_bass = track_end_bass(instrument);
400
401// Render the first section with the specified tracks and info.
402// The result will already be a rendered buffer
403let section_begin = section!(info_begin,
404 track_begin_melody,
405 track_begin_bass,
406);
407
408let section_end = section!(info_end,
409 track_end_melody,
410 track_end_bass,
411);
412
413// The rendered sections are put together to create the whole music piece.
414let composition = composition!(
415 section_begin,
416 section_end,
417);
418
419export(composition, "my_beautiful_piece.wav");
420
421fn export(buffer: SoundBuffer, name: &str) {
422 use std::path::PathBuf;
423
424 // Create struct with relevant info about exporting
425 // This will create a file on the file system.
426 let exporter = WavExport {
427 path: PathBuf::from(name),
428 ..Default::default()
429 };
430 exporter.export(buffer).unwrap();
431}
432#
433# fn track_begin_melody<T>(instrument: T) -> UnboundTrack<TET12ScaledTone, T>
434# where
435# T: Instrument<ConcreteValue = TET12ConcreteTone>
436# {
437# return UnboundTrack::new(instrument);
438# }
439#
440# fn track_begin_bass<T>(instrument: T) -> UnboundTrack<TET12ScaledTone, T>
441# where
442# T: Instrument<ConcreteValue = TET12ConcreteTone>
443# {
444# return UnboundTrack::new(instrument);
445# }
446#
447# fn track_end_melody<T>(instrument: T) -> UnboundTrack<TET12ScaledTone, T>
448# where
449# T: Instrument<ConcreteValue = TET12ConcreteTone>
450# {
451# return UnboundTrack::new(instrument);
452# }
453#
454# fn track_end_bass<T>(instrument: T) -> UnboundTrack<TET12ScaledTone, T>
455# where
456# T: Instrument<ConcreteValue = TET12ConcreteTone>
457# {
458# return UnboundTrack::new(instrument);
459# }
460```
461
462## UnboundTrack vs. MeasureTrack
463
464As of now there are two implementations for MusicTrack to place notes on a
465track.
466
467Before anything, it's recommended to use **MeasureTrack** for regular use,
468because it enforces more rules from music theory. Most examples use UnboundTrack
469for more compact code and concentration on the presented point.
470
471### MeasureTrack
472
473MeasureTrack is used for placing notes *and* measure bounds. A measure must
474always be filled with the right amount of notes and breaks to be "saturated".
475Trying to work with unsaturated threads will likely result in a panic.
476
477This enforcing of measure bounds serves to prevent mistakes where the measure
478bounds are violated and the track becomes desynchronized with the rest.
479
480MeasureTrack also provides access to the time signature features. More on this
481is [here](`composer::TimeSignature`).
482
483```rust
484use synth_music::prelude::*;
485use tet12::*;
486use length::*;
487
488let instrument = predefined::SineGenerator;
489// 4/4 Time
490let time_signature = TimeSignature::new(4, 4);
491
492let mut track = MeasureTrack::new(instrument, time_signature);
493track.set_intensity(0.7);
494
495// After four Quarters the measure must end
496sequential_notes!(track, QUARTER,
497 first(3),
498 second(3),
499 third(3),
500 fourth(3)
501);
502track.measure().unwrap();
503sequential_notes!(track, QUARTER,
504 fifth(3),
505 sixth(3),
506 seventh(3),
507 first(4)
508);
509// The last measure must also be "placed" with this call.
510track.measure().unwrap();
511```
512
513Now an example that is wrong:
514
515```should_panic
516use synth_music::prelude::*;
517use tet12::*;
518use length::*;
519
520let instrument = predefined::SineGenerator;
521let time_signature = TimeSignature::new(4, 4);
522
523let mut track = MeasureTrack::new(instrument, time_signature);
524track.set_intensity(0.7);
525
526sequential_notes!(track, QUARTER,
527 first(3),
528 second(3),
529 third(3),
530 fourth(3)
531);
532track.measure().unwrap();
533sequential_notes!(track, QUARTER,
534 fifth(3),
535 sixth(3),
536 seventh(3)
537 // missing a note; there should be a break here
538);
539track.measure().unwrap(); // panics here
540
541sequential_notes!(track, QUARTER,
542 fifth(3),
543 sixth(3),
544 seventh(3),
545 first(4),
546 second(4) // one note too much, the measure should've ended earlier
547);
548track.measure().unwrap(); // panics here
549
550sequential_notes!(track, QUARTER,
551 fifth(3),
552 sixth(3),
553 seventh(3),
554 first(4)
555);
556// not calling track.measure() will not place these notes in the track.
557```
558
559### UnboundTrack
560
561UnboundTrack is like MeasureTrack without Measures. One can arbitrarily place
562notes, the "position" of the notes is not enforced to be anywhere. The user can
563also resort to this implementation if the manual placing of Measures is too
564tedious.
565
566This implementation is best used for small scale tests and not complicated
567melodies. It's easy to accidentally break the regularity of the music, and there
568are no measure boundaries that serve as orientation points for the user.
569
570```rust
571use synth_music::prelude::*;
572use tet12::*;
573use length::*;
574
575let mut track = UnboundTrack::new(predefined::SineGenerator);
576track.set_intensity(0.7);
577
578// There is no limit to placing notes
579sequential_notes!(track, EIGTH.dot(),
580 first(3),
581 second(3),
582 third(3),
583 fourth(3),
584 fifth(3),
585 sixth(3),
586 seventh(3)
587);
588
589sequential_notes!(track, QUARTER.triole(),
590 first(4),
591 third(4),
592 first(4)
593);
594```
595
596### Custom implementation
597
598The user can also provide their own implementation for Tracks. For more info,
599check [`composer::MusicTrack`].
600
601*/
602
603pub mod composer;
604pub mod instrument;
605pub mod file_export;
606pub mod prelude;
607#[doc(hidden)]
608pub mod progress_bars;
609
610#[cfg(test)]
611mod tests {
612 #[test]
613 fn it_works() {
614 assert_eq!(4, 2 + 2);
615 }
616}