Skip to content

Instantly share code, notes, and snippets.

@tripulse
Last active November 6, 2019 09:44
Show Gist options
  • Save tripulse/5d15b5a938e9209cbe5ae0b216fbfe49 to your computer and use it in GitHub Desktop.
Save tripulse/5d15b5a938e9209cbe5ae0b216fbfe49 to your computer and use it in GitHub Desktop.
A simple encoder which encodes a "sine wave" with arbitrary frequency, amplitude, phase and samplerate into a MPEG Layer III file using "libshine".
#include <iostream>
#include <cmath>
#include <istream>
#include <vector>
#include <shine/layer3.h>
namespace Generators {
/* PI constant of Archimedes. Used to generate
sinusoids in this context. */
constexpr double PI = 3.14159265358979323846;
template<typename T> T maprange
(T x, T a, T b, T c, T d) {
return b + (x-a)*(d-c)/(b-a);
}
template<typename T> class SineWave {
public:
size_t sample_rate;
double phase;
double frequency;
std::vector<T> RenderSamples()
{
/* Holds all the sample data with <T> sampleformat. */
std::vector<T> _samples;
/* Resize the vector to the sample_rate because,
it's the samplerate is it's limit */
_samples.resize(sample_rate);
/* Limits of integers in the sampleformat */
std::numeric_limits<T> sfmt_lims;
for (auto s = 0; s < sample_rate; ++s) {
_samples[s] = (T)maprange<double>(std::sin((2.0f * PI *
((double)s / (double)sample_rate))
- phase
), -1, +1,
sfmt_lims.lowest(),
sfmt_lims.max());
}
return _samples;
}
};
}
/* Directly wraps a type of `CharT` into a StreamBuf
NOTE: this doesn't implement any protection, so
segfaults can happen. */
template<typename CharT, typename TraitsT = std::char_traits<CharT>>
class CharTypeIntoStreamBuf: public std::basic_streambuf<CharT, TraitsT> {
public:
CharTypeIntoStreamBuf(CharT *raw_data, size_t raw_size) {
this->setg(raw_data, raw_data, raw_data + raw_size);
}
};
int main() {
shine_t encoder;
shine_config_t encoder_config;
/* Set the MP3 file to output with the default configuration.
The configuration of the PCM stream is only modified. */
shine_set_config_mpeg_defaults(&encoder_config.mpeg);
/* Storing sinewave into streo doesn't make sense,
and generator also outputs MONO signals. */
encoder_config.wave.channels = PCM_MONO;
/* Amount of PCM data in bytes the encoder would process
on each encode call. */
int buffer_size = shine_samples_per_pass(encoder);
/* Create a source to node retrieve the data from
the sinusoid is rendered into PCM samples. */
Generators::SineWave<int16_t> osc_sinewave;
osc_sinewave.frequency = 200.0f;
osc_sinewave.phase = 0.0f;
osc_sinewave.sample_rate = encoder_config.wave.samplerate;
/* Stores the PCM buffer with 16-bit singed samples. */
std::vector<int16_t> pcm_buffer;
std::vector<int16_t> _tmp_samplebuf = osc_sinewave.RenderSamples();
/* Generate a 30 second sinewave. And, render it to
16-bit signed samples. */
constexpr uint32_t
DURATION = 30;
for(auto pos = 0U; pos < DURATION; ++pos) {
_tmp_samplebuf = osc_sinewave.RenderSamples();
pcm_buffer.insert(pcm_buffer.end(), _tmp_samplebuf.begin(), _tmp_samplebuf.end());
}
/* Wrap the samples into a filestream to read a certian
amount of data and automatically advance the read head.
*/
CharTypeIntoStreamBuf<char> _pcm_bufio ((char*)pcm_buffer.data(), pcm_buffer.size());
std::istream pcm_bufio (&_pcm_bufio);
char* pcm_reader_buffer = new char [buffer_size];
while (1)
{
int written_bytes = 0;
pcm_bufio.read(pcm_reader_buffer, buffer_size);
// Encode the RAW PCM buffer into MPEG Layer III format.
unsigned char* mpeg_out =
shine_encode_buffer_interleaved(encoder, (int16_t*)pcm_reader_buffer, &written_bytes);
/* Write encoded MPEG data into STDOUT. The data
could be piped to a program or be written to a file. */
std::fwrite(mpeg_out, 1, written_bytes, stdout);
/* Break if everything is encoded. */
if(written_bytes == 0)
break;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment