Skip to content

Instantly share code, notes, and snippets.

@chbtoys
Created July 17, 2022 09:47
Show Gist options
  • Select an option

  • Save chbtoys/ed489aa83edbb0c71d2a6d7efd245c41 to your computer and use it in GitHub Desktop.

Select an option

Save chbtoys/ed489aa83edbb0c71d2a6d7efd245c41 to your computer and use it in GitHub Desktop.
audio programming - beginner level
// compile: clang++ -std=c++20 -lpng 09-vectorsynth.cpp -o 09-vectorsynth
#define _USE_MATH_DEFINES
#include <cmath>
#include <vector>
#include <random>
#include <filesystem>
#include "indicators.hpp"
#include <png++/png.hpp>
#include "AudioFile/AudioFile.h"
#include "Envelope.hpp"
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 generatevectorsynth(std::vector<float>& v, int duration, double frequencyInHz);
void saveaudiofile(std::vector<float>& v, std::string filename);
}
int main()
{
namespace fs = std::filesystem;
fs::create_directory("vectorsynth");
const int duration=1;
const double sampleRate=44100.0;
std::vector<float> vectorsynth;
std::vector<float> envelope;
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{"Setup Vector Synth 1/2"},
option::ForegroundColor{Color::cyan},
option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}
};
// Update progress
bar.set_progress(0);
SignalGenerators::generatevectorsynth(vectorsynth,duration,440);
SignalGenerators::saveaudiofile(vectorsynth,"01-vectorsynth.aiff");
Render::saveimagefile(vectorsynth,"02-vectorsynth.png");
// Update progress
bar.set_progress(50);
bar.set_option(option::PostfixText{"Generating: Vector Synth 2/2"});
ADSR::Envelope env(sampleRate,duration);
env.generateenvelope(envelope);
env.applyenvelope(vectorsynth,envelope);
SignalGenerators::saveaudiofile(vectorsynth,"03-final_vectorsynth.aiff");
Render::saveimagefile(vectorsynth,"04-final_vectorsynth.png");
// Update progress
bar.set_progress(100);
bar.set_option(option::PostfixText{"Done 2/2"});
// 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 generatevectorsynth(std::vector<float>& v, int duration, double frequencyInHz)
{
float vectortransition[4][4]={{1.0,0.5,0.25,0.0},{0.5,1.0,0.0,0.25},{0.25,0.0,1.0,0.5},{0.0,0.25,0.5,1.0}};
const double sampleRate=44100.0;
std::vector<float> sample1;
std::vector<float> sample2;
std::vector<float> sample3;
std::vector<float> sample4;
generatetrianglewave(sample1,duration,frequencyInHz);
generateinversesawtoothwave(sample2,duration,frequencyInHz);
generatesawtoothwave(sample3,duration,frequencyInHz);
generatesquarewave(sample4,duration,frequencyInHz);
int ptr=0;
for (int i=0;i<4;++i) {
for (int j=0;j<11025;++j) {
sample1[ptr]=sample1[ptr]*vectortransition[i][0];
sample2[ptr]=sample2[ptr]*vectortransition[i][1];
sample3[ptr]=sample3[ptr]*vectortransition[i][2];
sample4[ptr]=sample4[ptr]*vectortransition[i][3];
ptr++;
}
}
v.resize(sample1.size());
addwaves(sample1,sample2,v);
addwaves(v,sample3,v);
addwaves(v,sample4,v);
normalize(v);
}
void saveaudiofile(std::vector<float>& v, std::string filename)
{
const std::string path="vectorsynth/";
// 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="vectorsynth/";
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="vectorsynth/";
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="vectorsynth/";
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="vectorsynth/";
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