diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index 3ea48b5..b747649 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,12 @@ if(PULSEAUDIO_FOUND) include_directories(${PULSEAUDIO_INCLUDE_DIR}) endif() -set(SINESYNTH_LIBS ${PULSEAUDIO_LIBRARY} pulse-simple) +find_package(Curses) +if (CURSES_FOUND) + include_directories(${CURSES_INCLUDE_DIRS}) +endif() + +set(SINESYNTH_LIBS ${PULSEAUDIO_LIBRARY} ${CURSES_LIBRARIES} pulse-simple tinfo) file(GLOB SOURCES "src/*.cpp") diff --git a/include/SineSynth.h b/include/SineSynth.h old mode 100644 new mode 100755 index 46314cb..39b39d2 --- a/include/SineSynth.h +++ b/include/SineSynth.h @@ -1,49 +1,106 @@ #pragma once -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +using std::shared_ptr; using std::vector; -using std::iterator_traits; -using std::sin; using std::cos; +using std::for_each; +using std::sin; -class Generator -{ +class Generator { public: - virtual void getSamples(const uint16_t nSamples, float *buf) = 0; + virtual float getSample() = 0; + virtual void getSamples(size_t nSamples, float* buf); + + virtual float getFrequency() const; + virtual void setFrequency(float _frequency); + + virtual bool isEnabled() const; + virtual void enable(); + virtual void disable(); + virtual ~Generator(); // disallow copy of virtual interface - Generator(Generator const &) = delete; - Generator &operator=(Generator const &) = delete; + Generator(Generator const&) = delete; + Generator& operator=(Generator const&) = delete; protected: Generator(uint32_t _sampleRate, float _frequency, float _amplitude); uint32_t sampleRate; float frequency; float amplitude; + bool enabled; private: }; -class SineGenerator : public Generator -{ +class SineGenerator : public Generator { public: SineGenerator(uint32_t _sampleRate, float _frequency, float _amplitude); - void getSamples(uint16_t nSamples, float *buf) final; + + float getSample() final; + + void setFrequency(float _frequency) final; + virtual ~SineGenerator(); private: - uint32_t tick; + float instPhase; float phaseIncr; }; -class SineSynth -{ +class NoiseGenerator : public Generator { +public: + NoiseGenerator(uint32_t _sampleRate, uint8_t rate, float _amplitude); + + float getSample() final; + virtual ~NoiseGenerator(); + + uint8_t rate; + static const uint16_t rateTable[]; + private: - vector generators; + int16_t level; + uint16_t sampleCount; +}; + +class SampleGenerator : public Generator { +public: + SampleGenerator(uint32_t _sampleRate, uint16_t _playbackRate, float _amplitude, size_t nSamples, const float* samples); + SampleGenerator(uint32_t _sampleRate, uint16_t _playbackRate, float _amplitude, size_t nSamples, const int16_t* samples); + + float getSample() final; + virtual ~SampleGenerator(); + + uint16_t playbackRate; + +private: + vector sample; + float oldest, older, old; + uint32_t counter; + static const uint16_t gaussTable[]; +}; + +class SineSynth { +public: + SineSynth(uint32_t _sampleRate); + + void addSynth(shared_ptr); + vector> getSynths() const; + void clearSynths(); + + void getSamples(size_t nSamples, float* buf); + +private: + uint32_t sampleRate; + vector> generators; }; \ No newline at end of file diff --git a/src/SineSynth.cpp b/src/SineSynth.cpp old mode 100644 new mode 100755 index 59759fd..92a4383 --- a/src/SineSynth.cpp +++ b/src/SineSynth.cpp @@ -2,27 +2,195 @@ #include -Generator::Generator(uint32_t _sampleRate, float _frequency, float _amplitude) : sampleRate(_sampleRate), frequency(_frequency), amplitude(_amplitude) +Generator::Generator(uint32_t _sampleRate, float _frequency, float _amplitude) + : sampleRate(_sampleRate) + , frequency(_frequency) + , amplitude(_amplitude) + , enabled(false) { } -Generator::~Generator() +Generator::~Generator() {} + +void Generator::getSamples(size_t nSamples, float* buf) { -} - -SineGenerator::SineGenerator(uint32_t _sampleRate, float _frequency, float _amplitude = 1) : Generator(_sampleRate, _frequency, _amplitude), tick(0) { - phaseIncr = 2 * M_PI * frequency / sampleRate; - - std::cout << "Constructed SineGenerator with freq " << frequency << " phaseIncr " << phaseIncr << std::endl; -} - -SineGenerator::~SineGenerator() { - -} - -void SineGenerator::getSamples(uint16_t nSamples, float *buf) { - for (;nSamples>0;nSamples--) { - std::cout << "Generating tick " << tick << std::endl; - *buf++ = cos(phaseIncr * tick++) * amplitude; + for (; nSamples > 0; nSamples--) { + *buf++ = getSample(); } +} + +float Generator::getFrequency() const { return frequency; } + +void Generator::setFrequency(float _frequency) { frequency = _frequency; } + +bool Generator::isEnabled() const { return enabled; } + +void Generator::enable() { enabled = true; } + +void Generator::disable() { enabled = false; } + +SineGenerator::SineGenerator( + uint32_t _sampleRate, float _frequency, float _amplitude = 1) + : Generator(_sampleRate, _frequency, _amplitude) + , instPhase(0) +{ + setFrequency(_frequency); + enable(); + std::cout << "Constructed SineGenerator with freq " << frequency + << " phaseIncr " << phaseIncr << std::endl; +} + +SineGenerator::~SineGenerator() {} + +float SineGenerator::getSample() +{ + if (instPhase > 2 * M_PI) + instPhase -= 2 * M_PI; + return cos(instPhase += phaseIncr) * amplitude; +} + +void SineGenerator::setFrequency(float _frequency) +{ + frequency = _frequency; + phaseIncr = 2 * M_PI * frequency / sampleRate; +} + +SineSynth::SineSynth(uint32_t _sampleRate) + : sampleRate(_sampleRate) +{ +} + +void SineSynth::addSynth(shared_ptr synth) +{ + generators.push_back(synth); +} + +vector> SineSynth::getSynths() const +{ + return generators; +} + +void SineSynth::clearSynths() { generators.resize(0); } + +void SineSynth::getSamples(size_t nSamples, float* buf) +{ + for (; nSamples > 0; nSamples--) { + *buf = 0; + for_each(std::begin(generators), std::end(generators), + [buf](std::shared_ptr g) { + if (g->isEnabled()) + *buf += g->getSample(); + }); + buf++; + } +} + +const uint16_t NoiseGenerator::rateTable[] + = { 0, 2048, 1536, 1280, 1024, 768, 640, 512, 384, 320, 256, 192, 160, 128, + 96, 80, 64, 48, 40, 32, 24, 20, 16, 12, 10, 8, 6, 5, 4, 3, 2, 1 }; + +NoiseGenerator::NoiseGenerator( + uint32_t _sampleRate, uint8_t _rate, float _amplitude) + : Generator(_sampleRate, 0, _amplitude) + , level(-0x4000) + , sampleCount(0) + , rate(_rate) +{ + enable(); +} + +NoiseGenerator::~NoiseGenerator() {} + +float NoiseGenerator::getSample() +{ + if (rate == 0) + return 0; + if (sampleCount++ % rateTable[rate] == 0) { + auto retLevel = level; + level = ((level >> 1) & 0x3fff) + | (((level & 0x01) ^ ((level & 0x02) >> 1)) << 14); + return (retLevel / 32768.0f) * amplitude; + } + return (level / 32768.0f) * amplitude; +} + +const uint16_t SampleGenerator::gaussTable[] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, + 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, + 0x6, 0x7, 0x7, 0x7, 0x8, 0x8, 0x8, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xb, 0xb, + 0xb, 0xc, 0xc, 0xd, 0xd, 0xe, 0xe, 0xf, 0xf, 0xf, 0x10, 0x10, 0x11, 0x11, + 0x12, 0x13, 0x13, 0x14, 0x14, 0x15, 0x15, 0x16, 0x17, 0x17, 0x18, 0x18, + 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x20, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x40, 0x41, 0x42, 0x43, 0x45, 0x46, 0x47, + 0x49, 0x4a, 0x4c, 0x4d, 0x4e, 0x50, 0x51, 0x53, 0x54, 0x56, 0x57, 0x59, + 0x5a, 0x5c, 0x5e, 0x5f, 0x61, 0x63, 0x64, 0x66, 0x68, 0x6a, 0x6b, 0x6d, + 0x6f, 0x71, 0x73, 0x75, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x84, + 0x86, 0x89, 0x8b, 0x8d, 0x8f, 0x91, 0x93, 0x96, 0x98, 0x9a, 0x9c, 0x9f, + 0xa1, 0xa3, 0xa6, 0xa8, 0xab, 0xad, 0xaf, 0xb2, 0xb4, 0xb7, 0xba, 0xbc, + 0xbf, 0xc1, 0xc4, 0xc7, 0xc9, 0xcc, 0xcf, 0xd2, 0xd4, 0xd7, 0xda, 0xdd, + 0xe0, 0xe3, 0xe6, 0xe9, 0xec, 0xef, 0xf2, 0xf5, 0xf8, 0xfb, 0xfe, 0x101, + 0x104, 0x107, 0x10b, 0x10e, 0x111, 0x114, 0x118, 0x11b, 0x11e, 0x122, 0x125, + 0x129, 0x12c, 0x130, 0x133, 0x137, 0x13a, 0x13e, 0x141, 0x145, 0x148, 0x14c, + 0x150, 0x153, 0x157, 0x15b, 0x15f, 0x162, 0x166, 0x16a, 0x16e, 0x172, 0x176, + 0x17a, 0x17d, 0x181, 0x185, 0x189, 0x18d, 0x191, 0x195, 0x19a, 0x19e, 0x1a2, + 0x1a6, 0x1aa, 0x1ae, 0x1b2, 0x1b7, 0x1bb, 0x1bf, 0x1c3, 0x1c8, 0x1cc, 0x1d0, + 0x1d5, 0x1d9, 0x1dd, 0x1e2, 0x1e6, 0x1eb, 0x1ef, 0x1f3, 0x1f8, 0x1fc, 0x201, + 0x205, 0x20a, 0x20f, 0x213, 0x218, 0x21c, 0x221, 0x226, 0x22a, 0x22f, 0x233, + 0x238, 0x23d, 0x241, 0x246, 0x24b, 0x250, 0x254, 0x259, 0x25e, 0x263, 0x267, + 0x26c, 0x271, 0x276, 0x27b, 0x280, 0x284, 0x289, 0x28e, 0x293, 0x298, 0x29d, + 0x2a2, 0x2a6, 0x2ab, 0x2b0, 0x2b5, 0x2ba, 0x2bf, 0x2c4, 0x2c9, 0x2ce, 0x2d3, + 0x2d8, 0x2dc, 0x2e1, 0x2e6, 0x2eb, 0x2f0, 0x2f5, 0x2fa, 0x2ff, 0x304, 0x309, + 0x30e, 0x313, 0x318, 0x31d, 0x322, 0x326, 0x32b, 0x330, 0x335, 0x33a, 0x33f, + 0x344, 0x349, 0x34e, 0x353, 0x357, 0x35c, 0x361, 0x366, 0x36b, 0x370, 0x374, + 0x379, 0x37e, 0x383, 0x388, 0x38c, 0x391, 0x396, 0x39b, 0x39f, 0x3a4, 0x3a9, + 0x3ad, 0x3b2, 0x3b7, 0x3bb, 0x3c0, 0x3c5, 0x3c9, 0x3ce, 0x3d2, 0x3d7, 0x3dc, + 0x3e0, 0x3e5, 0x3e9, 0x3ed, 0x3f2, 0x3f6, 0x3fb, 0x3ff, 0x403, 0x408, 0x40c, + 0x410, 0x415, 0x419, 0x41d, 0x421, 0x425, 0x42a, 0x42e, 0x432, 0x436, 0x43a, + 0x43e, 0x442, 0x446, 0x44a, 0x44e, 0x452, 0x455, 0x459, 0x45d, 0x461, 0x465, + 0x468, 0x46c, 0x470, 0x473, 0x477, 0x47a, 0x47e, 0x481, 0x485, 0x488, 0x48c, + 0x48f, 0x492, 0x496, 0x499, 0x49c, 0x49f, 0x4a2, 0x4a6, 0x4a9, 0x4ac, 0x4af, + 0x4b2, 0x4b5, 0x4b7, 0x4ba, 0x4bd, 0x4c0, 0x4c3, 0x4c5, 0x4c8, 0x4cb, 0x4cd, + 0x4d0, 0x4d2, 0x4d5, 0x4d7, 0x4d9, 0x4dc, 0x4de, 0x4e0, 0x4e3, 0x4e5, 0x4e7, + 0x4e9, 0x4eb, 0x4ed, 0x4ef, 0x4f1, 0x4f3, 0x4f5, 0x4f6, 0x4f8, 0x4fa, 0x4fb, + 0x4fd, 0x4ff, 0x500, 0x502, 0x503, 0x504, 0x506, 0x507, 0x508, 0x50a, 0x50b, + 0x50c, 0x50d, 0x50e, 0x50f, 0x510, 0x511, 0x511, 0x512, 0x513, 0x514, 0x514, + 0x515, 0x516, 0x516, 0x517, 0x517, 0x517, 0x518, 0x518, 0x518, 0x518, 0x518, + 0x519, 0x519 }; + +SampleGenerator::SampleGenerator(uint32_t _sampleRate, uint16_t _playbackRate, + float _amplitude, size_t nSamples, const float* samples) + : Generator(_sampleRate, 0, _amplitude) + , playbackRate(_playbackRate) + , counter(0) +{ + oldest = older = old = 0; + enable(); + sample.resize(nSamples); + for (auto i = std::begin(sample); i != std::end(sample); i++) + *i = *samples++; +} + +SampleGenerator::~SampleGenerator() {} + +float SampleGenerator::getSample() +{ + counter += playbackRate; + if ((counter >> 12) > sample.size()) + counter &= 0xfff; + + auto gaussIndex = (counter >> 4) & 0x3f; + float out = ((gaussTable[0xff - gaussIndex] / 32768.0f * oldest) / 1024); + out += ((gaussTable[0x1ff - gaussIndex] / 32768.0f * older) / 1024); + out += ((gaussTable[0x100 + gaussIndex] / 32768.0f * old) / 1024); + out += ((gaussTable[0x000 + gaussIndex] / 32768.0f * sample[counter>>12]) / 1024); + out /= 2; + + oldest = older; + older = old; + old = sample[counter>>12]; + + return out * amplitude; } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp old mode 100644 new mode 100755 index 85fb4bf..fedc67c --- a/src/main.cpp +++ b/src/main.cpp @@ -1,23 +1,208 @@ +#include #include +#include +#include + +#include +#include #include #include "SineSynth.h" -int main(int argc, char *argv[]) { - SineGenerator sg(44100, 440.0f, 0.1); - static const pa_sample_spec ss = { - .format = PA_SAMPLE_FLOAT32, - .rate = 44100, - .channels = 1 - }; +WINDOW* win; - pa_simple *s = NULL; - int error; - s = pa_simple_new(NULL, argv[0], PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error); +constexpr int SAMPLERATE = 32000; +constexpr size_t BUFSIZE = 64; - float buf[1024]; - for (;;) { - sg.getSamples(1024, buf); - pa_simple_write(s, buf, sizeof(buf), &error); +void majorChord(SineSynth& synth, const double root) +{ + wprintw(win, "majorChord root: %f\n", root); + if (synth.getSynths().size() != 3) { + synth.clearSynths(); + auto sg = std::make_shared(SAMPLERATE, root, 0.1); + synth.addSynth(sg); + sg = std::make_shared( + SAMPLERATE, root * pow(2, 4.0 / 12.0), 0.1); + synth.addSynth(sg); + sg = std::make_shared( + SAMPLERATE, root * pow(2, 7.0 / 12.0), 0.1); + synth.addSynth(sg); + } else { + auto synths = synth.getSynths(); + synths[0]->setFrequency(root); + synths[1]->setFrequency(root * pow(2, 4.0 / 12.0)); + synths[2]->setFrequency(root * pow(2, 7.0 / 12.0)); } +} + +void minorChord(SineSynth& synth, const double root) +{ + wprintw(win, "minorChord root: %f\n", root); + if (synth.getSynths().size() != 3) { + synth.clearSynths(); + auto sg = std::make_shared(SAMPLERATE, root, 0.1); + synth.addSynth(sg); + sg = std::make_shared( + SAMPLERATE, root * pow(2, 3.0 / 12.0), 0.1); + synth.addSynth(sg); + sg = std::make_shared( + SAMPLERATE, root * pow(2, 7.0 / 12.0), 0.1); + synth.addSynth(sg); + } else { + auto synths = synth.getSynths(); + synths[0]->setFrequency(root); + synths[1]->setFrequency(root * pow(2, 3.0 / 12.0)); + synths[2]->setFrequency(root * pow(2, 7.0 / 12.0)); + } +} + +WINDOW* +init_curses() +{ + WINDOW* win = initscr(); + nodelay(win, TRUE); + keypad(win, TRUE); + cbreak(); + + return win; +} + +pa_simple* +init_pulse() +{ + static const pa_sample_spec ss = { .format = PA_SAMPLE_FLOAT32, + .rate = SAMPLERATE, + .channels = 1 }; + + int error = 0; + pa_simple* s = pa_simple_new(NULL, + "sinesynth", + PA_STREAM_PLAYBACK, + NULL, + "playback", + &ss, + NULL, + NULL, + &error); + if (error) { + std::cerr << "Error initializing PulseAudio: " << pa_strerror(error) << " (" + << error << ")" << std::endl; + exit(-1); + } + + return s; +} + +const float testSample[] = { 0.0, + 0.0980171403295606, + 0.19509032201612825, + 0.29028467725446233, + 0.3826834323650898, + 0.47139673682599764, + 0.5555702330196022, + 0.6343932841636455, + 0.7071067811865475, + 0.773010453362737, + 0.8314696123025452, + 0.8819212643483549, + 0.9238795325112867, + 0.9569403357322089, + 0.9807852804032304, + 0.9951847266721968, + 1.0, + 0.9951847266721969, + 0.9807852804032304, + 0.9569403357322089, + 0.9238795325112867, + 0.881921264348355, + 0.8314696123025455, + 0.7730104533627371, + 0.7071067811865476, + 0.6343932841636455, + 0.5555702330196022, + 0.47139673682599786, + 0.3826834323650899, + 0.2902846772544624, + 0.1950903220161286, + 0.09801714032956083, + 1.2246467991473532e-16, + -0.09801714032956059, + -0.19509032201612836, + -0.2902846772544621, + -0.38268343236508967, + -0.47139673682599764, + -0.555570233019602, + -0.6343932841636453, + -0.7071067811865475, + -0.7730104533627367, + -0.8314696123025452, + -0.8819212643483549, + -0.9238795325112865, + -0.9569403357322088, + -0.9807852804032303, + -0.9951847266721969, + -1.0, + -0.9951847266721969, + -0.9807852804032304, + -0.9569403357322089, + -0.9238795325112866, + -0.881921264348355, + -0.8314696123025455, + -0.7730104533627369, + -0.7071067811865477, + -0.6343932841636459, + -0.5555702330196022, + -0.4713967368259979, + -0.3826834323650904, + -0.2902846772544625, + -0.19509032201612872, + -0.0980171403295605 }; + +int main(int argc, char* argv[]) +{ + SineSynth synth(SAMPLERATE); + auto noise = std::make_shared(SAMPLERATE, 0x1f, 0.1); + //synth.addSynth(noise); + auto sampled = std::make_shared(SAMPLERATE, 0x1000, 0.1, sizeof(testSample)/sizeof(float), testSample); + synth.addSynth(sampled); + + sampled->enable(); + noise->disable(); + + auto s = init_pulse(); + auto win = init_curses(); + + mvwprintw(win, 0, 0, "Initialized\n"); + + int error = 0; + pa_usec_t latency = pa_simple_get_latency(s, &error); + wprintw(win, + "Error: %s (%d) Latency: %d usec\n", + pa_strerror(error), + error, + latency); + + float buf[BUFSIZE]; + long long i; + mvwprintw(win, 2, 0, "Loops: "); + mvwprintw(win, 3, 0, "Rate: "); + for (i = 0;; i++) { + auto c = getch(); + switch (c) { + case KEY_UP: + sampled->playbackRate+=8; + sampled->playbackRate &= 0x3fff; + break; + case KEY_DOWN: + sampled->playbackRate-=8; + sampled->playbackRate &= 0x3fff; + break; + } + synth.getSamples(BUFSIZE, buf); + pa_simple_write(s, buf, sizeof(buf), &error); + mvwprintw(win, 2, 8, "%d", i); + mvwprintw(win, 3, 7, "%2x", sampled->playbackRate); + } + + endwin(); } \ No newline at end of file