File size: 4,045 Bytes
9c6594c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"""
Each generator will return float samples from -1.0 to 1.0, which can be 
converted to actual audio with 8, 16, 24, or 32 bit depth using the
SiganlGenerator.to_audio_segment() method (on any of it's subclasses).

See Wikipedia's "waveform" page for info on some of the generators included 
here: http://en.wikipedia.org/wiki/Waveform
"""

import math
import array
import itertools
import random
from .audio_segment import AudioSegment
from .utils import (
    db_to_float,
    get_frame_width,
    get_array_type,
    get_min_max_value
)



class SignalGenerator(object):
    def __init__(self, sample_rate=44100, bit_depth=16):
        self.sample_rate = sample_rate
        self.bit_depth = bit_depth

    def to_audio_segment(self, duration=1000.0, volume=0.0):
        """
        Duration in milliseconds
            (default: 1 second)
        Volume in DB relative to maximum amplitude
            (default 0.0 dBFS, which is the maximum value)
        """
        minval, maxval = get_min_max_value(self.bit_depth)
        sample_width = get_frame_width(self.bit_depth)
        array_type = get_array_type(self.bit_depth)

        gain = db_to_float(volume)
        sample_count = int(self.sample_rate * (duration / 1000.0))

        sample_data = (int(val * maxval * gain) for val in self.generate())
        sample_data = itertools.islice(sample_data, 0, sample_count)

        data = array.array(array_type, sample_data)
        
        try:
            data = data.tobytes()
        except:
            data = data.tostring()

        return AudioSegment(data=data, metadata={
            "channels": 1,
            "sample_width": sample_width,
            "frame_rate": self.sample_rate,
            "frame_width": sample_width,
        })

    def generate(self):
        raise NotImplementedError("SignalGenerator subclasses must implement the generate() method, and *should not* call the superclass implementation.")



class Sine(SignalGenerator):
    def __init__(self, freq, **kwargs):
        super(Sine, self).__init__(**kwargs)
        self.freq = freq

    def generate(self):
        sine_of = (self.freq * 2 * math.pi) / self.sample_rate
        sample_n = 0
        while True:
            yield math.sin(sine_of * sample_n)
            sample_n += 1



class Pulse(SignalGenerator):
    def __init__(self, freq, duty_cycle=0.5, **kwargs):
        super(Pulse, self).__init__(**kwargs)
        self.freq = freq
        self.duty_cycle = duty_cycle

    def generate(self):
        sample_n = 0

        # in samples
        cycle_length = self.sample_rate / float(self.freq)
        pulse_length = cycle_length * self.duty_cycle

        while True:
            if (sample_n % cycle_length) < pulse_length:
                yield 1.0
            else:
                yield -1.0
            sample_n += 1



class Square(Pulse):
    def __init__(self, freq, **kwargs):
        kwargs['duty_cycle'] = 0.5
        super(Square, self).__init__(freq, **kwargs)



class Sawtooth(SignalGenerator):
    def __init__(self, freq, duty_cycle=1.0, **kwargs):
        super(Sawtooth, self).__init__(**kwargs)
        self.freq = freq
        self.duty_cycle = duty_cycle

    def generate(self):
        sample_n = 0

        # in samples
        cycle_length = self.sample_rate / float(self.freq)
        midpoint = cycle_length * self.duty_cycle
        ascend_length = midpoint
        descend_length = cycle_length - ascend_length

        while True:
            cycle_position = sample_n % cycle_length
            if cycle_position < midpoint:
                yield (2 * cycle_position / ascend_length) - 1.0
            else:
                yield 1.0 - (2 * (cycle_position - midpoint) / descend_length)
            sample_n += 1



class Triangle(Sawtooth):
    def __init__(self, freq, **kwargs):
        kwargs['duty_cycle'] = 0.5
        super(Triangle, self).__init__(freq, **kwargs)


class WhiteNoise(SignalGenerator):
    def generate(self):
        while True:
            yield (random.random() * 2) - 1.0