1 /* 2 Copyright (c) 2016-2021 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 /** 30 * Simplest waveform synthesizers 31 * 32 * Copyright: Timur Gafarov 2016-2021. 33 * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). 34 * Authors: Timur Gafarov 35 */ 36 module dlib.audio.synth; 37 38 import std.math; 39 import std.random; 40 import dlib.audio.sound; 41 42 /** 43 * An interface for a synthesizer that maps sample position to -1..1 sample value 44 */ 45 interface Synth 46 { 47 /** 48 * Evaluate a sample. 49 * 50 * Params: 51 * sound = a sound object which parameters are used to discretize a sample 52 * position = sample index 53 * frequency = signal frequency in Hz 54 */ 55 float eval(Sound sound, ulong position, float frequency); 56 } 57 58 /** 59 * Sine wave synthesizer 60 */ 61 class SineWaveSynth: Synth 62 { 63 /** 64 * Evaluate a sine wave sample. 65 * 66 * Params: 67 * sound = a sound object which parameters are used to discretize a sample 68 * position = sample index 69 * frequency = signal frequency in Hz 70 */ 71 float eval(Sound sound, ulong position, float frequency) 72 { 73 double samplePeriod = 1.0 / cast(double)sound.sampleRate; 74 double time = position * samplePeriod; 75 return sin(2.0 * PI * frequency * time); 76 } 77 } 78 79 /** 80 * Square wave synthesizer 81 */ 82 class SquareWaveSynth: Synth 83 { 84 /** 85 * Evaluate square wave sample. 86 * 87 * Params: 88 * sound = a sound object which parameters are used to discretize a sample 89 * position = sample index 90 * frequency = signal frequency in Hz 91 */ 92 float eval(Sound sound, ulong position, float frequency) 93 { 94 double samplePeriod = 1.0 / cast(double)sound.sampleRate; 95 double phase = position * samplePeriod * frequency; 96 double s = 2.0 * floor(phase) - floor(2.0 * phase) + 1.0; 97 return s * 2.0 - 1.0; 98 } 99 } 100 101 /** 102 * Sawtooth wave synthesizer 103 */ 104 class SawtoothWaveSynth: Synth 105 { 106 /** 107 * Evaluate sawtooth wave sample. 108 * 109 * Params: 110 * sound = a sound object which parameters are used to discretize a sample 111 * position = sample index 112 * frequency = signal frequency in Hz 113 */ 114 float eval(Sound sound, ulong position, float frequency) 115 { 116 double samplePeriod = 1.0 / cast(double)sound.sampleRate; 117 double phase = position * samplePeriod * frequency; 118 double s = phase - floor(phase); 119 return s * 2.0 - 1.0; 120 } 121 } 122 123 /** 124 * Triangle wave synthesizer 125 */ 126 class TriangleWaveSynth: Synth 127 { 128 /** 129 * Evaluate triangle wave sample. 130 * 131 * Params: 132 * sound = a sound object which parameters are used to discretize a sample 133 * position = sample index 134 * frequency = signal frequency in Hz 135 */ 136 float eval(Sound sound, ulong position, float frequency) 137 { 138 double samplePeriod = 1.0 / cast(double)sound.sampleRate; 139 double phase = position * samplePeriod * frequency; 140 double s = abs(1.0 - fmod(phase, 2.0)); 141 return s * 2.0 - 1.0; 142 } 143 } 144 145 /** 146 * Frequency modulation synthesizer 147 */ 148 class FMSynth: Synth 149 { 150 Synth carrier; 151 Synth modulator; 152 float frequencyRatio; 153 154 /** 155 * Constructor. 156 * 157 * Params: 158 * carrier = carrier waveform 159 * modulator = modulator waveform 160 * frequencyRatio = frequency multiplier for modulator 161 */ 162 this(Synth carrier, Synth modulator, float frequencyRatio) 163 { 164 this.carrier = carrier; 165 this.modulator = modulator; 166 this.frequencyRatio = frequencyRatio; 167 } 168 169 /** 170 * Evaluate FM sample. 171 * 172 * Params: 173 * sound = a sound object which parameters are used to discretize a sample 174 * position = sample index 175 * frequency = signal frequency in Hz 176 */ 177 float eval(Sound sound, ulong position, float frequency) 178 { 179 float m = modulator.eval(sound, position, frequency * frequencyRatio); 180 return carrier.eval(sound, position, frequency + m); 181 } 182 } 183 184 // TODO: EnvelopeSynth 185 186 /** 187 * Fill a given portion of a sound with a signal from specified synthesizer. 188 * Params: 189 * sound = a sound object to write to 190 * channel = channel to fill (beginning from 0) 191 * synth = synthesizer object 192 * freq = synthesizer frequency 193 * startTime = start time of a signal in seconds 194 * duration = duration of a signal in seconds 195 * amplitude = volume coefficient of a signal 196 */ 197 void fillSynth(Sound sound, uint channel, Synth synth, float freq, double startTime, double duration, float amplitude) 198 { 199 ulong startSample = cast(ulong)(startTime * sound.sampleRate); 200 ulong endSample = startSample + cast(ulong)(duration * sound.sampleRate); 201 if (endSample >= sound.size) 202 endSample = sound.size - 1; 203 204 foreach(i; startSample..endSample) 205 { 206 sound[channel, i] = synth.eval(sound, i - startSample, freq) * amplitude; 207 } 208 } 209 210 /** 211 * Additively mix a signal from specified synthesizer to a given portion of a sound. 212 * sound = a sound object to write to 213 * channel = channel to fill (beginning from 0) 214 * synth = synthesizer object 215 * freq = synthesizer frequency 216 * startTime = start time of a signal in seconds 217 * duration = duration of a signal in seconds 218 * amplitude = volume coefficient of a signal 219 */ 220 void mixSynth(Sound sound, uint channel, Synth synth, float freq, double startTime, double duration, float amplitude) 221 { 222 ulong startSample = cast(ulong)(startTime * sound.sampleRate); 223 ulong endSample = startSample + cast(ulong)(duration * sound.sampleRate); 224 if (endSample >= sound.size) 225 endSample = sound.size - 1; 226 227 foreach(i; startSample..endSample) 228 { 229 float src = sound[channel, i]; 230 sound[channel, i] = src + synth.eval(sound, i - startSample, freq) * amplitude; 231 } 232 } 233 234 /** 235 * Generate random audio signal. 236 * snd = sound 237 * ch = channel to fill (beginning from 0) 238 */ 239 void whiteNoise(Sound snd, uint ch) 240 { 241 foreach(i; 0..snd.size) 242 { 243 snd[ch, i] = uniform(-1.0f, 1.0f); 244 } 245 } 246 247 /** 248 * Fill the sound with simple sine wave tone. 249 * snd = sound 250 * ch = channel to fill (beginning from 0) 251 * freq = frequency in Hz. For example, a dial tone in Europe is usually 425 Hz 252 */ 253 void sineWave(Sound snd, uint ch, float freq) 254 { 255 float samplePeriod = 1.0f / cast(float)snd.sampleRate; 256 foreach(i; 0..snd.size) 257 { 258 snd[ch, i] = sin(samplePeriod * i * freq * 2.0f * PI); 259 } 260 }