Created
July 17, 2022 09:37
-
-
Save chbtoys/8c63f4c47e69adca5a10c1527fd21997 to your computer and use it in GitHub Desktop.
audio programming - beginner level
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// compile: clang++ -std=c++20 -lpng 02-subtractive.cpp -o 02-subtractive | |
#define _USE_MATH_DEFINES | |
#include <cmath> | |
#include <vector> | |
#include <random> | |
#include <filesystem> | |
#include "indicators.hpp" | |
#include <png++/png.hpp> | |
#include "AudioFile/AudioFile.h" | |
#include "MoogFilter.hpp" | |
#include "Envelope.hpp" | |
#include <iostream> | |
namespace Render | |
{ | |
void fillbackground(png::image<png::rgb_pixel>& image); | |
void drawpx(png::image<png::rgb_pixel>& image, int x, int y); | |
void drawline(png::image<png::rgb_pixel>& image, int x1, int y1, int x2, int y2); | |
void drawwave(png::image<png::rgb_pixel>& image,std::vector<uint32_t>& signalY); | |
void normalizedtoimg(png::image<png::rgb_pixel>& image, std::vector<float>& v1,std::vector<uint32_t>& v2); | |
void renderimage(png::image<png::rgb_pixel>& image,std::vector<uint32_t>& v); | |
void saveimagefile(std::vector<uint32_t>& v, std::string filename); | |
void saveimagefile(std::vector<float>& v2, std::string filename); | |
void normalizedenvelopetoimg(png::image<png::rgb_pixel>& image, std::vector<float>& v1,std::vector<uint32_t>& v2); | |
void saveenvelopeimage(std::vector<uint32_t>& v, std::string filename); | |
void saveenvelopeimage(std::vector<float>& v2, std::string filename); | |
} | |
namespace SignalGenerators | |
{ | |
void gain(std::vector<float>& v, double gain); | |
void normalize(std::vector<float>& v); | |
void addwaves(std::vector<float>& v1,std::vector<float>& v2,std::vector<float>& v3); | |
void generatetrianglewave(std::vector<float>& v, int duration, double frequencyInHz); | |
void generateinversesawtoothwave(std::vector<float>& v, int duration, double frequencyInHz); | |
void generatesawtoothwave(std::vector<float>& v, int duration, double frequencyInHz); | |
void generatesquarewave(std::vector<float>& v, int duration, double frequencyInHz); | |
void generatethirtyfivesquarewave(std::vector<float>& v, int duration, double frequencyInHz); | |
void generatetwentyfivesquarewave(std::vector<float>& v, int duration, double frequencyInHz); | |
void generatenoise(std::vector<float>& v, int duration); | |
void generatelfo(std::vector<float>& v, int osc, int duration, double frequencyInHz); | |
void generateoscillator(std::vector<float>& v, int osc, int duration, double frequencyInHz); | |
void saveaudiofile(std::vector<float>& v, std::string filename); | |
} | |
int main() | |
{ | |
namespace fs = std::filesystem; | |
fs::create_directory("subtractive"); | |
enum osc: int { triangle, inversesaw, saw, square, thirty, twenty, noise }; | |
const int duration=1; | |
const double sampleRate=44100.0; | |
const double noiseGain=0.5; | |
std::vector<float> osc1; | |
std::vector<float> osc2; | |
std::vector<float> osc3; | |
std::vector<float> osc4; | |
std::vector<float> mixer; | |
std::vector<float> envelope; | |
Moog::MoogFilter mf(44100.0); | |
using namespace indicators; | |
// Hide cursor | |
show_console_cursor(false); | |
// Setup ProgressBar | |
ProgressBar bar{ | |
option::BarWidth{50}, | |
option::Start{"["}, | |
option::Fill{"■"}, | |
option::Lead{"■"}, | |
option::Remainder{"-"}, | |
option::End{" ]"}, | |
option::PostfixText{"Setting: Oscillator 1 to Triangle Wave @ 440 Hz 1/8"}, | |
option::ForegroundColor{Color::cyan}, | |
option::FontStyles{std::vector<FontStyle>{FontStyle::bold}} | |
}; | |
// Update progress | |
bar.set_progress(0); | |
SignalGenerators::generateoscillator(osc1,triangle,duration,440); | |
// Update progress | |
bar.set_progress(12); | |
bar.set_option(option::PostfixText{"Setting: Oscillator 2 to Triangle Wave @ 880 Hz 2/8"}); | |
SignalGenerators::generateoscillator(osc2,triangle,duration,440*2); | |
// Update progress | |
bar.set_progress(25); | |
bar.set_option(option::PostfixText{"Setting: Oscillator 3 to Triangle Wave @ 1320 Hz 3/8"}); | |
SignalGenerators::generateoscillator(osc3,triangle,duration,440*3); | |
// Update progress | |
bar.set_progress(37); | |
bar.set_option(option::PostfixText{"Setting: Oscillator 4 (The White Noise Oscillator) 4/8"}); | |
SignalGenerators::generateoscillator(osc4,noise,duration,440); | |
// SignalGenerators::generatelfo(lfo,inversesaw,duration,15); | |
// SignalGenerators::addwaves(osc3,lfo,osc3); | |
SignalGenerators::gain(osc4,noiseGain); | |
// Update progress | |
bar.set_progress(50); | |
bar.set_option(option::PostfixText{"Setting Oscillator 4 Gain to: 50% 5/8"}); | |
mixer.resize(duration*sampleRate); | |
SignalGenerators::addwaves(osc1,osc2,mixer); | |
SignalGenerators::normalize(mixer); | |
SignalGenerators::addwaves(mixer,osc3,mixer); | |
SignalGenerators::normalize(mixer); | |
Render::saveimagefile(mixer,"01-mixerpreosc4.png"); | |
SignalGenerators::saveaudiofile(mixer,"02-mixerpreosc4.aiff"); | |
SignalGenerators::addwaves(mixer,osc4,mixer); | |
SignalGenerators::normalize(mixer); | |
Render::saveimagefile(mixer,"03-mixerpostosc4.png"); | |
SignalGenerators::saveaudiofile(mixer,"04-mixerpostosc4.aiff"); | |
// Update progress | |
bar.set_progress(62); | |
bar.set_option(option::PostfixText{"Mixed Oscillator 1 to 4 together 6/8"}); | |
Render::saveimagefile(mixer,"05-prefilter.png"); | |
SignalGenerators::saveaudiofile(mixer,"06-prefilter.aiff"); | |
mf.Process(mixer,mixer.size()); | |
SignalGenerators::normalize(mixer); | |
Render::saveimagefile(mixer,"07-postfilter.png"); | |
SignalGenerators::saveaudiofile(mixer,"08-postfilter.aiff"); | |
// Update progress | |
bar.set_progress(75); | |
bar.set_option(option::PostfixText{"Adding MoogFilter to the mixed signal 7/8"}); | |
ADSR::Envelope env(sampleRate,duration); | |
env.generateenvelope(envelope); | |
Render::saveenvelopeimage(envelope,"09-envelope.png"); | |
// Update progress | |
bar.set_progress(87); | |
bar.set_option(option::PostfixText{"Adding ADSR Envelope to the mixed signal 8/8"}); | |
env.applyenvelope(mixer,envelope); | |
Render::saveimagefile(mixer,"10-envelopedmix.png"); | |
SignalGenerators::saveaudiofile(mixer,"11-envelopedmix.aiff"); | |
// Update progress | |
bar.set_progress(100); | |
bar.set_option(option::PostfixText{"Done 8/8"}); | |
// Show cursor | |
show_console_cursor(true); | |
return 0; | |
} | |
namespace SignalGenerators | |
{ | |
void gain(std::vector<float>& v, double gain) | |
{ | |
for (uint32_t i=0;i<v.size();++i) { | |
v[i]=v[i]*gain; | |
} | |
} | |
void normalize(std::vector<float>& v) | |
{ | |
float max=0.0,value=0.0; | |
for (uint32_t i=0;i<v.size();++i) { | |
value=v[i]; | |
if (value > max) {max=value;} | |
} | |
// max=std::ceil(max); | |
for (uint32_t i=0;i<v.size();++i) { | |
// v[i]=v[i]/max; | |
v[i]=(v[i]/max)*0.707; | |
} | |
} | |
void addwaves(std::vector<float>& v1,std::vector<float>& v2,std::vector<float>& v3) | |
{ | |
for (uint32_t i=0;i<v1.size();++i) | |
{ | |
v3[i]=v1[i]+v2[i]; | |
} | |
} | |
void generatetrianglewave(std::vector<float>& v, int duration, double frequencyInHz) | |
{ | |
const double sampleRate=44100.0; | |
for (uint32_t i=0;i<sampleRate*duration;++i) { | |
v.push_back(M_2_PI*asin(sin(frequencyInHz*2*M_PI*i/sampleRate))); | |
} | |
} | |
void generateinversesawtoothwave(std::vector<float>& v, int duration, double frequencyInHz) | |
{ | |
const double sampleRate=44100.0; | |
double period=sampleRate/frequencyInHz; | |
std::vector<float> temp; | |
for (uint32_t i=0;i<period;++i) { | |
temp.push_back(-2/M_PI*atan(1/tan(frequencyInHz*M_PI*i/sampleRate))); | |
} | |
for (uint32_t i=0;i<sampleRate*duration;++i) { | |
v.push_back(temp[temp.size()-(i%temp.size())]); | |
} | |
} | |
void generatesawtoothwave(std::vector<float>& v, int duration, double frequencyInHz) | |
{ | |
const double sampleRate=44100.0; | |
for (uint32_t i=0;i<sampleRate*duration;++i) { | |
v.push_back(-2/M_PI*atan(1/tan(frequencyInHz*M_PI*i/sampleRate))); | |
} | |
} | |
void generatesquarewave(std::vector<float>& v, int duration, double frequencyInHz) | |
{ | |
const double sampleRate=44100.0; | |
double period=sampleRate/frequencyInHz; | |
double dutyCycle=period*0.5; | |
double ss=0.0; | |
for (uint32_t i=0;i<sampleRate*duration;++i) { | |
if ((i%int(period)) >= 0 && (i%int(period)) < int(dutyCycle)) | |
{ | |
ss=0.7; | |
} else { | |
ss=-0.7; | |
} | |
v.push_back(ss); | |
} | |
v[0]=0.0; | |
v[v.size()-1]=0.0; | |
} | |
void generatethirtyfivesquarewave(std::vector<float>& v, int duration, double frequencyInHz) | |
{ | |
const double sampleRate=44100.0; | |
double period=sampleRate/frequencyInHz; | |
double dutyCycle=period*0.35; | |
double ss; | |
for (uint32_t i=0;i<sampleRate*duration;++i) { | |
if ((i%int(period)) >= 0 && (i%int(period)) < int(dutyCycle)) | |
{ | |
ss=0.7; | |
} else { | |
ss=-0.7; | |
} | |
v.push_back(ss); | |
} | |
} | |
void generatetwentyfivesquarewave(std::vector<float>& v, int duration, double frequencyInHz) | |
{ | |
const double sampleRate=44100.0; | |
double period=sampleRate/frequencyInHz; | |
double dutyCycle=period*0.25; | |
double ss; | |
for (uint32_t i=0;i<sampleRate*duration;++i) { | |
if ((i%int(period)) >= 0 && (i%int(period)) < int(dutyCycle)) | |
{ | |
ss=0.7; | |
} else { | |
ss=-0.7; | |
} | |
v.push_back(ss); | |
} | |
} | |
void generatenoise(std::vector<float>& v, int duration) | |
{ | |
const double sampleRate=44100.0; | |
std::random_device rd; | |
std::mt19937 gen(rd()); | |
std::uniform_real_distribution<> dis(-1.0, 1.0); | |
for (uint32_t i=0;i<sampleRate*duration;++i) { | |
v.push_back(dis(gen)); | |
} | |
} | |
void generatelfo(std::vector<float>& v, int osc, int duration, double frequencyInHz) | |
{ | |
if (frequencyInHz >= 0.0 && frequencyInHz <= 20.0) { | |
switch(osc) { | |
case 0: generatetrianglewave(v,duration,frequencyInHz); | |
break; | |
case 1: generateinversesawtoothwave(v,duration,frequencyInHz); | |
break; | |
case 2: generatesawtoothwave(v,duration,frequencyInHz); | |
break; | |
case 3: generatesquarewave(v,duration,frequencyInHz); | |
break; | |
case 4: generatethirtyfivesquarewave(v,duration,frequencyInHz); | |
break; | |
case 5: generatetwentyfivesquarewave(v,duration,frequencyInHz); | |
break; | |
} | |
} | |
} | |
void generateoscillator(std::vector<float>& v, int osc, int duration, double frequencyInHz) | |
{ | |
if (frequencyInHz >= 20.0 && frequencyInHz <= 20000.0) { | |
switch(osc) { | |
case 0: generatetrianglewave(v,duration,frequencyInHz); | |
break; | |
case 1: generateinversesawtoothwave(v,duration,frequencyInHz); | |
break; | |
case 2: generatesawtoothwave(v,duration,frequencyInHz); | |
break; | |
case 3: generatesquarewave(v,duration,frequencyInHz); | |
break; | |
case 4: generatethirtyfivesquarewave(v,duration,frequencyInHz); | |
break; | |
case 5: generatetwentyfivesquarewave(v,duration,frequencyInHz); | |
break; | |
case 6: generatenoise(v,duration); | |
break; | |
} | |
} | |
} | |
void saveaudiofile(std::vector<float>& v, std::string filename) | |
{ | |
const std::string path="subtractive/"; | |
// Setup the audio file | |
AudioFile<float> a; | |
a.setNumChannels(1); | |
a.setBitDepth(24); | |
a.setNumSamplesPerChannel(44100); | |
for (int i=0;i<a.getNumSamplesPerChannel();++i) | |
{ | |
for (int channel=0;channel<a.getNumChannels();++channel) | |
{ | |
a.samples[channel][i]=v[i]; | |
} | |
} | |
a.save(path+filename,AudioFileFormat::Aiff); | |
} | |
} | |
namespace Render | |
{ | |
void fillbackground(png::image<png::rgb_pixel>& image) | |
{ | |
png::rgb_pixel px(0x04,0x13,0x31); | |
for (uint32_t y=0;y<image.get_height();y++) { | |
for (uint32_t x=0;x<image.get_width();++x) { | |
image.set_pixel(x,y,px); | |
} | |
} | |
} | |
void drawpx(png::image<png::rgb_pixel>& image, int x, int y) | |
{ | |
if (((x >= 0) && (x < image.get_width())) && ((y >= 0) && (y < image.get_height()))) | |
{ | |
png::rgb_pixel px(0x7a,0xb1,0xe3); | |
image.set_pixel(x,y,px); | |
} | |
} | |
void drawline(png::image<png::rgb_pixel>& image, int x1, int y1, int x2, int y2) | |
{ | |
int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; | |
dx = x2 - x1; dy = y2 - y1; | |
if (dx == 0) | |
{ | |
if (y2 < y1) std::swap(y1, y2); | |
for (y = y1; y <= y2; y++) | |
drawpx(image, x1, y); | |
return; | |
} | |
if (dy == 0) | |
{ | |
if (x2 < x1) std::swap(x1, x2); | |
for (x = x1; x <= x2; x++) | |
drawpx(image, x, y1); | |
return; | |
} | |
dx1 = abs(dx); dy1 = abs(dy); | |
px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; | |
if (dy1 <= dx1) | |
{ | |
if (dx >= 0) | |
{ | |
x = x1; y = y1; xe = x2; | |
} | |
else | |
{ | |
x = x2; y = y2; xe = x1; | |
} | |
drawpx(image, x, y); | |
for (i = 0; x<xe; i++) | |
{ | |
x = x + 1; | |
if (px<0) | |
px = px + 2 * dy1; | |
else | |
{ | |
if ((dx<0 && dy<0) || (dx>0 && dy>0)) y = y + 1; else y = y - 1; | |
px = px + 2 * (dy1 - dx1); | |
} | |
drawpx(image, x, y); | |
} | |
} | |
else | |
{ | |
if (dy >= 0) | |
{ | |
x = x1; y = y1; ye = y2; | |
} | |
else | |
{ | |
x = x2; y = y2; ye = y1; | |
} | |
drawpx(image, x, y); | |
for (i = 0; y<ye; i++) | |
{ | |
y = y + 1; | |
if (py <= 0) | |
py = py + 2 * dx1; | |
else | |
{ | |
if ((dx<0 && dy<0) || (dx>0 && dy>0)) x = x + 1; else x = x - 1; | |
py = py + 2 * (dx1 - dy1); | |
} | |
drawpx(image, x, y); | |
} | |
} | |
} | |
void drawwave(png::image<png::rgb_pixel>& image,std::vector<uint32_t>& signalY) | |
{ | |
uint32_t y=0,ox=0,oy=0; | |
for (uint32_t x=0;x<image.get_width();++x) | |
{ | |
y=signalY[x]; | |
if (x == 0) {ox=x;oy=y;} | |
drawline(image,x,y,ox,oy); | |
ox=x;oy=y; | |
} | |
} | |
void normalizedtoimg(png::image<png::rgb_pixel>& image, std::vector<float>& v1,std::vector<uint32_t>& v2) | |
{ | |
uint32_t halfHeight=image.get_height()/2; | |
double value=0.0; | |
if (v2.size() == 0 || v2.size() > v1.size()) { | |
v2.resize(v1.size()); | |
} | |
for (uint32_t i=0;i<v1.size();++i) | |
{ | |
value=v1[i]; | |
if (value >= 0.0) { | |
v2[i]=halfHeight-(halfHeight*value); | |
} else if (value < 0.0) | |
{ | |
v2[i]=halfHeight+(halfHeight*fabs(value)); | |
} | |
} | |
} | |
void renderimage(png::image<png::rgb_pixel>& image,std::vector<uint32_t>& v) | |
{ | |
fillbackground(image); | |
drawwave(image,v); | |
} | |
void saveimagefile(std::vector<uint32_t>& v, std::string filename) | |
{ | |
const std::string path="subtractive/"; | |
png::image<png::rgb_pixel> image(44100,600); | |
renderimage(image,v); | |
image.write(path+filename); | |
} | |
void saveimagefile(std::vector<float>& v2, std::string filename) | |
{ | |
const std::string path="subtractive/"; | |
png::image<png::rgb_pixel> image(44100,600); | |
std::vector<uint32_t> v; | |
normalizedtoimg(image,v2,v); | |
renderimage(image,v); | |
image.write(path+filename); | |
} | |
void normalizedenvelopetoimg(png::image<png::rgb_pixel>& image, std::vector<float>& v1,std::vector<uint32_t>& v2) | |
{ | |
uint32_t height=image.get_height(); | |
if (v2.size() == 0 || v2.size() > v1.size()) { | |
v2.resize(v1.size()); | |
} | |
for (uint32_t i=0;i<v1.size();++i) | |
{ | |
v2[i]=height-(height*v1[i]); | |
} | |
} | |
void saveenvelopeimage(std::vector<uint32_t>& v, std::string filename) | |
{ | |
const std::string path="subtractive/"; | |
png::image<png::rgb_pixel> image(44100,600); | |
renderimage(image,v); | |
image.write(path+filename); | |
} | |
void saveenvelopeimage(std::vector<float>& v2, std::string filename) | |
{ | |
const std::string path="subtractive/"; | |
png::image<png::rgb_pixel> image(44100,600); | |
std::vector<uint32_t> v; | |
normalizedenvelopetoimg(image,v2,v); | |
renderimage(image,v); | |
image.write(path+filename); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment