Skip to content

Instantly share code, notes, and snippets.

@chbtoys
Created July 17, 2022 09:37
Show Gist options
  • Save chbtoys/8c63f4c47e69adca5a10c1527fd21997 to your computer and use it in GitHub Desktop.
Save chbtoys/8c63f4c47e69adca5a10c1527fd21997 to your computer and use it in GitHub Desktop.
audio programming - beginner level
// 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