Created
July 17, 2022 09:52
-
-
Save chbtoys/45bf343a9b339d44be389e644c75cf7e 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 12-physicalmodelling_karplus_strong.cpp -o 12-physicalmodelling_karplus_strong | |
#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 generatekarplusstrong(std::vector<float>& v, int duration, double frequencyInHz, double feedback); | |
void saveaudiofile(std::vector<float>& v, std::string filename); | |
} | |
int main() | |
{ | |
namespace fs = std::filesystem; | |
fs::create_directory("physicalmodelling"); | |
const int duration=1; | |
const double sampleRate=44100.0; | |
const double feedback=0.2; | |
std::vector<float> karplusstrong; | |
int octave=3; | |
int note=0; | |
double frequencyInHz=float(440 * pow(2.0, ((double)((octave - 4) * 12 + note)) / 12.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{"Generate Physical Modelling 1/1"}, | |
option::ForegroundColor{Color::cyan}, | |
option::FontStyles{std::vector<FontStyle>{FontStyle::bold}} | |
}; | |
// Update progress | |
bar.set_progress(0); | |
SignalGenerators::generatekarplusstrong(karplusstrong,duration,frequencyInHz,0.996); | |
SignalGenerators::normalize(karplusstrong); | |
SignalGenerators::saveaudiofile(karplusstrong,"01-karplusstrong.aiff"); | |
Render::saveimagefile(karplusstrong,"02-karplusstrong.png"); | |
// Update progress | |
bar.set_progress(100); | |
bar.set_option(option::PostfixText{"Done 1/1"}); | |
// Show cursor | |
show_console_cursor(true); | |
return 0; | |
} | |
namespace SignalGenerators | |
{ | |
class KSString | |
{ | |
public: | |
KSString(float frequencyInHz, float sampleRate, float feedback) | |
{ | |
std::random_device rd; | |
std::mt19937 gen(rd()); | |
std::uniform_real_distribution<> dis(-1.0, 1.0); | |
m_buffer.resize(uint32_t(float(sampleRate) / frequencyInHz)); | |
for (size_t i = 0, c = m_buffer.size(); i < c; ++i) { | |
m_buffer[i] = dis(gen); | |
} | |
m_index = 0; | |
m_feedback = feedback; | |
} | |
float GenerateSample() | |
{ | |
// get our sample to return | |
float ret = m_buffer[m_index]; | |
// low pass filter (average) some samples | |
float value = (m_buffer[m_index] + m_buffer[(m_index + 1) % m_buffer.size()]) * 0.5f * m_feedback; | |
m_buffer[m_index] = value; | |
// move to the next sample | |
m_index = (m_index + 1) % m_buffer.size(); | |
// return the sample from the buffer | |
return ret; | |
} | |
private: | |
std::vector<float> m_buffer; | |
size_t m_index; | |
float m_feedback; | |
}; | |
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 generatekarplusstrong(std::vector<float>& v, int duration, double frequencyInHz, double feedback) | |
{ | |
const double sampleRate=44100.0; | |
KSString pluck(frequencyInHz,sampleRate,0.996f); | |
v.resize(sampleRate); | |
for (uint32_t i=0;i<v.size();++i) { | |
v[i]=0; | |
v[i]+=pluck.GenerateSample(); | |
} | |
} | |
void saveaudiofile(std::vector<float>& v, std::string filename) | |
{ | |
const std::string path="physicalmodelling/"; | |
// 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="physicalmodelling/"; | |
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="physicalmodelling/"; | |
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="physicalmodelling/"; | |
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="physicalmodelling/"; | |
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