1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! Generative ambient music.
//!
//! Layering multiple notes like this leads to interesting emergent effects, such as constructive
//! and destructive interference, and beating from the [septimal
//! comma](https://en.wikipedia.org/wiki/Septimal_comma) 64/63 = 4/3 × 4/3 × 4/7.
//!
//! Sounds even smoother after some post-processing reverb.

use pointillism::prelude::*;
use rand::Rng;

/// Project sample rate.
const SAMPLE_RATE: SampleRate = SampleRate::CD;

/// Possible values to multiply a frequency by.
const MULTS: [f64; 6] = [
    4.0 / 3.0,
    3.0 / 4.0,
    3.0 / 2.0,
    2.0 / 3.0,
    7.0 / 4.0,
    4.0 / 7.0,
];

fn main() {
    // Frequency of base note.
    const BASE: RawFreq = RawFreq::new(400.0);
    // Length of each note.
    const NOTE_LEN: RawTime = RawTime::new(3.0);
    // Length of released note.
    const RELEASE_LEN: RawTime = RawTime::new(45.0);

    // Length of song in notes.
    const NOTE_COUNT_LEN: u16 = 200;
    // Number of notes actually played (accounting for fade-out).
    const NOTE_COUNT: u16 = 185;

    let note_len = Time::from_raw(NOTE_LEN, SAMPLE_RATE);

    // Envelope for the wave shape.
    let shape_env = Comp::new(Saw, Linear::rescale_sgn(0.75, 0.5));

    // Each oscillator is a function of frequency and panning angle.
    let osc = |freq, angle| {
        pointillism::effects::pan::Panner::mixed(
            MutSgn::new(
                AdsrEnvelope::new_adsr(
                    // Saw-triangle wave with specified frequency.
                    LoopGen::new(SawTri::saw(), freq),
                    // ADSR envelope with long attack, very long release.
                    note_len,
                    Time::ZERO,
                    Vol::FULL,
                    Time::from_raw(RELEASE_LEN, SAMPLE_RATE),
                ),
                OnceGen::new(shape_env, note_len),
                // Smoothly interpolates between a saw and a triangle wave.
                FnWrapper::new(
                    |sgn: &mut AdsrEnvelope<LoopGen<Stereo, SawTri>>, val: Env| {
                        sgn.sgn_mut().curve_mut().shape = val.0;
                    },
                ),
            ),
            angle,
        )
    };

    // Base frequency.
    let base = Freq::from_raw(BASE, SAMPLE_RATE);
    // Frequency of note being played.
    let mut freq = base;

    // Initializes a new `Polyphony` object, plays a single note, centered.
    let mut poly = Polyphony::new();
    let mut index = 0;
    poly.add(index, osc(freq, 0.5));

    let note_len = Time::from_raw(NOTE_LEN, SAMPLE_RATE);

    // The song loop.
    let poly_loop = Loop::new(
        vec![note_len],
        poly,
        FnWrapper::new(|poly: &mut Polyphony<_, _>| {
            // Stops the previous note.
            poly.stop(&index);
            index += 1;

            // Changes the frequency randomly.
            freq *= MULTS[rand::thread_rng().gen_range(0..MULTS.len())];

            // Clamps the frequency between two octaves.
            if freq >= 2.0 * base {
                freq /= 2.0;
            } else if freq <= base / 2.0 {
                freq *= 2.0;
            }

            // Plays a new note, as long as the song isn't about to end.
            if index < NOTE_COUNT {
                poly.add(index, osc(freq, rand::thread_rng().gen()));
            }
        }),
    );

    pointillism::create_from_sgn(
        "examples/continuum.wav",
        NOTE_COUNT_LEN * note_len,
        SAMPLE_RATE,
        // 10.0 might be too much, but just to be safe from clipping.
        Volume::new(poly_loop, Vol::new(1.0 / 10.0)),
    )
    .unwrap();
}