Commit af85c13c authored by Stefan Westerfeld's avatar Stefan Westerfeld

Merge branch 'sync'

parents 6fc77233 9c30e309
......@@ -3,6 +3,7 @@
#include <string>
#include <random>
#include <complex>
#include <algorithm>
#include "fft.hh"
#include "wavdata.hh"
......@@ -26,13 +27,23 @@ namespace Params
static size_t bands_per_frame = 30;
static int max_band = 100;
static int min_band = 20;
static double water_delta = 0.015; // strength of the watermark
static double water_delta = 0.01; // strength of the watermark
static double pre_scale = 0.95; // rescale the signal to avoid clipping after watermark is added
static bool mix = true;
static bool hard = false; // hard decode bits? (soft decoding is better)
static int block_size = 32; // block size for mix step (non-linear bit storage)
static int have_key = 0;
static size_t payload_size = 128; // number of payload bits for the watermark
static int sync_bits = 6;
static int sync_frames_per_bit = 85;
static int sync_search_step = 256;
static int sync_search_fine = 8;
static double sync_threshold1 = 0.5; // minimum grid quality value (search_step grid)
static double sync_threshold2 = 0.7; // minimum refined quality
static size_t frames_pad_start = 250; // padding at start, in case track starts with silence
static int test_cut = 0; // for sync test
}
void
......@@ -172,6 +183,10 @@ parse_options (int *argc_p,
Params::have_key++;
Random::load_global_key (opt_arg);
}
else if (check_arg (argc, argv, &i, "--test-cut", &opt_arg))
{
Params::test_cut = atoi (opt_arg);
}
}
/* resort argc/argv */
......@@ -220,46 +235,17 @@ db_from_factor (double factor, double min_dB)
int
frame_count (const WavData& wav_data)
{
return (wav_data.n_values() / wav_data.n_channels() + (Params::frame_size - 1)) / Params::frame_size;
}
int
block_count (const WavData& wav_data)
{
return frame_count (wav_data) / (Params::block_size * Params::frames_per_bit);
}
/*
* get one audio frame, Params::frame_size samples if available
*
* in case of stereo: deinterleave
*/
vector<float>
get_frame (const WavData& wav_data, int f, int ch)
{
auto& samples = wav_data.samples();
vector<float> result;
size_t pos = (f * Params::frame_size) * wav_data.n_channels() + ch;
for (size_t x = 0; x < Params::frame_size; x++)
{
if (pos < samples.size())
result.push_back (samples[pos]);
pos += wav_data.n_channels();
}
return result;
return wav_data.n_values() / wav_data.n_channels() / Params::frame_size;
}
void
get_up_down (int f, vector<int>& up, vector<int>& down)
get_up_down (int f, vector<int>& up, vector<int>& down, Random::Stream random_stream)
{
vector<int> bands_reorder;
for (int i = Params::min_band; i <= Params::max_band; i++)
bands_reorder.push_back (i);
Random random (f, Random::Stream::up_down); // use per frame random seed
Random random (f, random_stream); // use per frame random seed
random.shuffle (bands_reorder);
assert (2 * Params::bands_per_frame < bands_reorder.size());
......@@ -292,6 +278,104 @@ randomize_bit_order (const vector<T>& bit_vec, bool encode)
return out_bits;
}
vector<vector<complex<float>>>
compute_frame_ffts (const WavData& wav_data, size_t start_index, size_t frame_count)
{
vector<vector<complex<float>>> fft_out;
/* if there is not enough space for frame_count values, return an error (empty vector) */
if (wav_data.n_values() < (start_index + frame_count * Params::frame_size) * wav_data.n_channels())
return fft_out;
/* generate analysis window */
vector<float> window (Params::frame_size);
double window_weight = 0;
for (size_t i = 0; i < Params::frame_size; i++)
{
const double fsize_2 = Params::frame_size / 2.0;
// const double win = window_cos ((i - fsize_2) / fsize_2);
const double win = window_hamming ((i - fsize_2) / fsize_2);
//const double win = 1;
window[i] = win;
window_weight += win;
}
/* normalize window using window weight */
for (size_t i = 0; i < Params::frame_size; i++)
{
window[i] *= 2.0 / window_weight;
}
float *frame = new_array_float (Params::frame_size);
float *frame_fft = new_array_float (Params::frame_size);
for (size_t f = 0; f < frame_count; f++)
{
for (int ch = 0; ch < wav_data.n_channels(); ch++)
{
const auto& samples = wav_data.samples();
size_t pos = (start_index + f * Params::frame_size) * wav_data.n_channels() + ch;
assert (pos + (Params::frame_size - 1) * wav_data.n_channels() < samples.size());
/* deinterleave frame data and apply window */
for (size_t x = 0; x < Params::frame_size; x++)
{
frame[x] = samples[pos] * window[x];
pos += wav_data.n_channels();
}
/* FFT transform */
fftar_float (Params::frame_size, frame, frame_fft);
/* complex<float> and frame_fft have the same layout in memory */
const complex<float> *first = (complex<float> *) frame_fft;
const complex<float> *last = first + Params::frame_size / 2 + 1;
fft_out.emplace_back (first, last);
}
}
free_array_float (frame);
free_array_float (frame_fft);
return fft_out;
}
void
mark_bit_linear (int f, const vector<complex<float>>& fft_out, vector<complex<float>>& fft_delta_spect, int data_bit, Random::Stream random_stream)
{
vector<int> up;
vector<int> down;
get_up_down (f, up, down, random_stream);
const double data_bit_sign = data_bit > 0 ? 1 : -1;
for (auto u : up)
{
/*
* for up bands, we want do use [for a 1 bit] (pow (mag, 1 - water_delta))
*
* this actually increases the amount of energy because mag is less than 1.0
*/
const float mag_factor = pow (abs (fft_out[u]), -Params::water_delta * data_bit_sign);
fft_delta_spect[u] = fft_out[u] * (mag_factor - 1);
}
for (auto d : down)
{
/*
* for down bands, we want do use [for a 1 bit] (pow (mag, 1 + water_delta))
*
* this actually decreases the amount of energy because mag is less than 1.0
*/
const float mag_factor = pow (abs (fft_out[d]), Params::water_delta * data_bit_sign);
fft_delta_spect[d] = fft_out[d] * (mag_factor - 1);
}
}
size_t
mark_data_frame_count()
{
return conv_code_size (Params::payload_size) * Params::frames_per_bit;
}
struct MixEntry
{
int frame;
......@@ -300,67 +384,118 @@ struct MixEntry
};
vector<MixEntry>
gen_mix_entries (int block)
gen_mix_entries()
{
vector<MixEntry> mix_entries;
for (int f = 0; f < Params::block_size * Params::frames_per_bit; f++)
for (int f = 0; f < int (mark_data_frame_count()); f++)
{
vector<int> up;
vector<int> down;
get_up_down (f, up, down);
get_up_down (f, up, down, Random::Stream::data_up_down);
assert (up.size() == down.size());
for (size_t i = 0; i < up.size(); i++)
mix_entries.push_back ({ f, up[i], down[i] });
}
Random random (/* seed */ block, Random::Stream::mix);
Random random (/* seed */ 0, Random::Stream::mix);
random.shuffle (mix_entries);
return mix_entries;
}
vector<vector<complex<float>>>
compute_frame_ffts (const WavData& wav_data)
void
mark_data (const WavData& wav_data, int start_frame, const vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_delta_spect,
const vector<int>& bitvec)
{
vector<vector<complex<float>>> fft_out;
assert (fft_out.size() >= (start_frame + mark_data_frame_count()) * wav_data.n_channels());
assert (bitvec.size() == mark_data_frame_count() / Params::frames_per_bit);
/* generate analysis window */
vector<float> window (Params::frame_size);
const int frame_count = mark_data_frame_count();
double window_weight = 0;
for (size_t i = 0; i < Params::frame_size; i++)
if (Params::mix)
{
const double fsize_2 = Params::frame_size / 2.0;
// const double win = window_cos ((i - fsize_2) / fsize_2);
const double win = window_hamming ((i - fsize_2) / fsize_2);
//const double win = 1;
window[i] = win;
window_weight += win;
}
vector<MixEntry> mix_entries = gen_mix_entries();
/* normalize window using window weight */
for (size_t i = 0; i < Params::frame_size; i++)
for (int f = 0; f < frame_count; f++)
{
for (int ch = 0; ch < wav_data.n_channels(); ch++)
{
for (size_t frame_b = 0; frame_b < Params::bands_per_frame; frame_b++)
{
int b = f * Params::bands_per_frame + frame_b;
const int data_bit = bitvec[f / Params::frames_per_bit];
const double data_bit_sign = data_bit > 0 ? 1 : -1;
const int u = mix_entries[b].up;
const int index = (start_frame + mix_entries[b].frame) * wav_data.n_channels() + ch;
{
const float mag_factor = pow (abs (fft_out[index][u]), -Params::water_delta * data_bit_sign);
fft_delta_spect[index][u] = fft_out[index][u] * (mag_factor - 1);
}
const int d = mix_entries[b].down;
{
const float mag_factor = pow (abs (fft_out[index][d]), Params::water_delta * data_bit_sign);
fft_delta_spect[index][d] = fft_out[index][d] * (mag_factor - 1);
}
}
}
}
}
else
{
window[i] *= 2.0 / window_weight;
for (int f = 0; f < frame_count; f++)
{
for (int ch = 0; ch < wav_data.n_channels(); ch++)
{
size_t index = (start_frame + f) * wav_data.n_channels() + ch;
mark_bit_linear (f, fft_out[index], fft_delta_spect[index], bitvec[f / Params::frames_per_bit], Random::Stream::data_up_down);
}
}
}
}
size_t
mark_sync_frame_count()
{
return Params::sync_bits * Params::sync_frames_per_bit;
}
for (int f = 0; f < frame_count (wav_data); f++)
void
mark_sync (const WavData& wav_data, int start_frame, const vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_delta_spect)
{
assert (fft_out.size() >= (start_frame + mark_sync_frame_count()) * wav_data.n_channels());
const int frame_count = mark_sync_frame_count();
// sync block always written in linear order (no mix)
for (int f = 0; f < frame_count; f++)
{
for (int ch = 0; ch < wav_data.n_channels(); ch++)
{
vector<float> frame = get_frame (wav_data, f, ch);
/* apply window */
for (size_t i = 0; i < frame.size(); i++)
frame[i] *= window[i];
size_t index = (start_frame + f) * wav_data.n_channels() + ch;
int data_bit = (f / Params::sync_frames_per_bit) & 1; /* write 010101 */
/* FFT transform */
fft_out.push_back (fft (frame));
mark_bit_linear (f, fft_out[index], fft_delta_spect[index], data_bit, Random::Stream::sync_up_down);
}
}
return fft_out;
}
void
mark_pad (const WavData& wav_data, size_t frame, const vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_delta_spect)
{
assert (fft_out.size() >= (frame + 1) * wav_data.n_channels());
for (int ch = 0; ch < wav_data.n_channels(); ch++)
{
size_t index = frame * wav_data.n_channels() + ch;
mark_bit_linear (frame, fft_out[index], fft_delta_spect[index], 0, Random::Stream::pad_up_down);
}
}
int
......@@ -388,6 +523,9 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
/* add forward error correction, bitvec will now be a lot larger */
bitvec = randomize_bit_order (conv_encode (bitvec), /* encode */ true);
/* pad with zeros to match block_size */
bitvec.resize (mark_data_frame_count() / Params::frames_per_bit);
printf ("loading %s\n", infile.c_str());
WavData in_wav_data;
......@@ -399,10 +537,10 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
/*
* to keep the watermarking code simpler, we pad the wave data with zeros
* to avoid processing a partly filled block
* to avoid processing a partly filled frame
*/
vector<float> in_signal (in_wav_data.samples());
while (in_signal.size() % (in_wav_data.n_channels() * Params::frame_size * Params::block_size * Params::frames_per_bit))
while (in_signal.size() % (in_wav_data.n_channels() * Params::frame_size))
in_signal.push_back (0);
WavData wav_data (in_signal, in_wav_data.n_channels(), in_wav_data.mix_freq(), in_wav_data.bit_depth());
......@@ -411,7 +549,7 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
vector<float> out_signal (wav_data.n_values());
printf ("channels: %d, samples: %zd, mix_freq: %f\n", wav_data.n_channels(), wav_data.n_values(), wav_data.mix_freq());
vector<vector<complex<float>>> fft_out = compute_frame_ffts (wav_data);
vector<vector<complex<float>>> fft_out = compute_frame_ffts (wav_data, 0, frame_count (wav_data));
vector<vector<complex<float>>> fft_delta_spect;
for (int f = 0; f < frame_count (wav_data); f++)
{
......@@ -420,81 +558,27 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
fft_delta_spect.push_back (vector<complex<float>> (fft_out.back().size()));
}
}
if (Params::mix)
size_t frame_index = 0;
/* padding at start */
while (frame_index < Params::frames_pad_start)
{
for (int block = 0; block < block_count (wav_data); block++)
{
vector<MixEntry> mix_entries = gen_mix_entries (block);
const int block_start = block * Params::block_size * Params::frames_per_bit;
for (int f = 0; f < Params::block_size * Params::frames_per_bit; f++)
{
for (int ch = 0; ch < wav_data.n_channels(); ch++)
{
for (size_t frame_b = 0; frame_b < Params::bands_per_frame; frame_b++)
{
int b = f * Params::bands_per_frame + frame_b;
const int data_bit = bitvec[((block_start + f) / Params::frames_per_bit) % bitvec.size()];
const double data_bit_sign = data_bit > 0 ? 1 : -1;
const int u = mix_entries[b].up;
const int index = (block_start + mix_entries[b].frame) * wav_data.n_channels() + ch;
{
const float mag_factor = pow (abs (fft_out[index][u]), -Params::water_delta * data_bit_sign);
fft_delta_spect[index][u] = fft_out[index][u] * (mag_factor - 1);
}
const int d = mix_entries[b].down;
{
const float mag_factor = pow (abs (fft_out[index][d]), Params::water_delta * data_bit_sign);
fft_delta_spect[index][d] = fft_out[index][d] * (mag_factor - 1);
}
}
}
}
}
mark_pad (wav_data, frame_index, fft_out, fft_delta_spect);
frame_index++;
}
else
/* embed sync|data|sync|data|... */
while (frame_index + (mark_sync_frame_count() + mark_data_frame_count()) < size_t (frame_count (wav_data)))
{
for (int f = 0; f < frame_count (wav_data); f++)
{
for (int ch = 0; ch < wav_data.n_channels(); ch++)
{
size_t index = f * wav_data.n_channels() + ch;
mark_sync (wav_data, frame_index, fft_out, fft_delta_spect);
frame_index += mark_sync_frame_count();
vector<int> up;
vector<int> down;
get_up_down (f, up, down);
const int data_bit = bitvec[(f / Params::frames_per_bit) % bitvec.size()];
const double data_bit_sign = data_bit > 0 ? 1 : -1;
for (auto u : up)
{
/*
* for up bands, we want do use [for a 1 bit] (pow (mag, 1 - water_delta))
*
* this actually increases the amount of energy because mag is less than 1.0
*/
const float mag_factor = pow (abs (fft_out[index][u]), -Params::water_delta * data_bit_sign);
fft_delta_spect[index][u] = fft_out[index][u] * (mag_factor - 1);
}
for (auto d : down)
{
/*
* for down bands, we want do use [for a 1 bit] (pow (mag, 1 + water_delta))
*
* this actually decreases the amount of energy because mag is less than 1.0
*/
const float mag_factor = pow (abs (fft_out[index][d]), Params::water_delta * data_bit_sign);
fft_delta_spect[index][d] = fft_out[index][d] * (mag_factor - 1);
}
}
}
mark_data (wav_data, frame_index, fft_out, fft_delta_spect, bitvec);
frame_index += mark_data_frame_count();
}
/* padding at end */
while (frame_index < size_t (frame_count (wav_data)))
{
mark_pad (wav_data, frame_index, fft_out, fft_delta_spect);
frame_index++;
}
/* generate synthesis window */
......@@ -525,7 +609,6 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
// cosine
synth_window[i] = (cos (tri*M_PI+M_PI)+1) * 0.5;
}
for (size_t pos = 0; pos < in_signal.size(); pos++)
out_signal[pos] = in_signal[pos] * Params::pre_scale;
......@@ -568,16 +651,6 @@ add_watermark (const string& infile, const string& outfile, const string& bits)
return 0;
}
void
truncate_to_block_size (WavData& wav_data)
{
vector<float> in_signal (wav_data.samples());
while (in_signal.size() % (wav_data.n_channels() * Params::frame_size * Params::block_size * Params::frames_per_bit))
in_signal.pop_back();
wav_data.set_samples (in_signal);
}
vector<float>
normalize_soft_bits (const vector<float>& soft_bits)
{
......@@ -606,63 +679,63 @@ normalize_soft_bits (const vector<float>& soft_bits)
}
vector<float>
mix_decode (const WavData& wav_data, vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_orig_out)
mix_decode (vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_orig_out, int n_channels)
{
vector<float> soft_bit_vec;
vector<float> raw_bit_vec;
for (int block = 0; block < block_count (wav_data); block++)
{
vector<MixEntry> mix_entries = gen_mix_entries (block);
const int frame_count = fft_out.size() / n_channels;
double umag = 0, dmag = 0;
for (int f = 0; f < Params::block_size * Params::frames_per_bit; f++)
vector<MixEntry> mix_entries = gen_mix_entries();
double umag = 0, dmag = 0;
for (int f = 0; f < frame_count; f++)
{
for (int ch = 0; ch < n_channels; ch++)
{
for (int ch = 0; ch < wav_data.n_channels(); ch++)
for (size_t frame_b = 0; frame_b < Params::bands_per_frame; frame_b++)
{
for (size_t frame_b = 0; frame_b < Params::bands_per_frame; frame_b++)
{
int b = f * Params::bands_per_frame + frame_b;
const double min_db = -96;
int b = f * Params::bands_per_frame + frame_b;
const double min_db = -96;
const size_t index = (block * (Params::block_size * Params::frames_per_bit) + mix_entries[b].frame) * wav_data.n_channels() + ch;
const int u = mix_entries[b].up;
const int d = mix_entries[b].down;
const size_t index = mix_entries[b].frame * n_channels + ch;
const int u = mix_entries[b].up;
const int d = mix_entries[b].down;
umag += db_from_factor (abs (fft_out[index][u]), min_db);
dmag += db_from_factor (abs (fft_out[index][d]), min_db);
umag += db_from_factor (abs (fft_out[index][u]), min_db);
dmag += db_from_factor (abs (fft_out[index][d]), min_db);
if (index < fft_orig_out.size()) /* non-blind decode? */
{
umag -= db_from_factor (abs (fft_orig_out[index][u]), min_db);
dmag -= db_from_factor (abs (fft_orig_out[index][d]), min_db);
}
if (index < fft_orig_out.size()) /* non-blind decode? */
{
umag -= db_from_factor (abs (fft_orig_out[index][u]), min_db);
dmag -= db_from_factor (abs (fft_orig_out[index][d]), min_db);
}
}
if ((f % Params::frames_per_bit) == (Params::frames_per_bit - 1))
{
soft_bit_vec.push_back (umag - dmag);
umag = 0;
dmag = 0;
}
}
if ((f % Params::frames_per_bit) == (Params::frames_per_bit - 1))
{
raw_bit_vec.push_back (umag - dmag);
umag = 0;
dmag = 0;
}
}
return normalize_soft_bits (soft_bit_vec);
return raw_bit_vec;
}
vector<float>
linear_decode (const WavData& wav_data, vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_orig_out)
linear_decode (vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_orig_out, int n_channels)
{
vector<float> soft_bit_vec;
vector<float> raw_bit_vec;
double umag = 0, dmag = 0;
for (int f = 0; f < frame_count (wav_data); f++)
const int frame_count = fft_out.size() / n_channels;
for (int f = 0; f < frame_count; f++)
{
for (int ch = 0; ch < wav_data.n_channels(); ch++)
for (int ch = 0; ch < n_channels; ch++)
{
const size_t index = f * wav_data.n_channels() + ch;
const size_t index = f * n_channels + ch;
vector<int> up;
vector<int> down;
get_up_down (f, up, down);
get_up_down (f, up, down, Random::Stream::data_up_down);
const double min_db = -96;
for (auto u : up)
......@@ -682,52 +755,311 @@ linear_decode (const WavData& wav_data, vector<vector<complex<float>>>& fft_out,
}
if ((f % Params::frames_per_bit) == (Params::frames_per_bit - 1))
{
soft_bit_vec.push_back (umag - dmag);
raw_bit_vec.push_back (umag - dmag);
umag = 0;
dmag = 0;
}
}
return normalize_soft_bits (soft_bit_vec);
return raw_bit_vec;
}
double
normalize_sync_quality (double raw_quality)
{
/* the quality for a good sync block depends on watermark strength
*
* this is just an approximation, but it should be good enough to be able to
* use one single threshold on the normalized value check if we have a sync
* block or not - typical output is 1.0 or more for sync blocks and close
* to 0.0 for non-sync blocks
*/
return raw_quality / min (Params::water_delta, 0.080) / 2.9;
}
class SyncFinder
{
vector<vector<int>> up;
vector<vector<int>> down;
void
init_up_down (const WavData& wav_data)
{
up.clear();
down.clear();
up.resize (Params::sync_bits);
down.resize (Params::sync_bits);
size_t n_bands = Params::max_band - Params::min_band + 1;
for (int bit = 0; bit < Params::sync_bits; bit++)
{
for (int f = 0; f < Params::sync_frames_per_bit; f++)
{
vector<int> frame_up, frame_down;
get_up_down (f + bit * Params::sync_frames_per_bit, frame_up, frame_down, Random::Stream::sync_up_down);
for (auto u : frame_up)
up[bit].push_back (u - Params::min_band + f * n_bands * wav_data.n_channels());
for (auto d : frame_down)
down[bit].push_back (d - Params::min_band + f * n_bands * wav_data.n_channels());
}
sort (up[bit].begin(), up[bit].end());
sort (down[bit].begin(), down[bit].end());
}
}
double
sync_decode (const WavData& wav_data, const size_t start_frame, const vector<float>& fft_out_db)
{
double sync_quality = 0;
size_t n_bands = Params::max_band - Params::min_band + 1;
for (int bit = 0; bit < Params::sync_bits; bit++)
{
float umag = 0, dmag = 0;
for (int ch = 0; ch < wav_data.n_channels(); ch++)
{
const int index = (((bit * Params::sync_frames_per_bit) + start_frame) * wav_data.n_channels() + ch) * n_bands;
for (size_t i = 0; i < up[bit].size(); i++)
{
umag += fft_out_db[index + up[bit][i]];
dmag += fft_out_db[index + down[bit][i]];
}
}
/* convert avoiding bias, raw_bit < 0 => 0 bit received; raw_bit > 0 => 1 bit received */
double raw_bit;
if (umag < dmag)
{
raw_bit = 1 - umag / dmag;
}
else
{
raw_bit = dmag / umag - 1;
}
const int expect_data_bit = bit & 1; /* expect 010101 */
const double q = expect_data_bit ? raw_bit : -raw_bit;
sync_quality += q;
}
sync_quality /= Params::sync_bits;
sync_quality = normalize_sync_quality (sync_quality);
return sync_quality;
}
public:
struct Score {
size_t index;
double quality;
};
vector<Score>
search (const WavData& wav_data)
{
vector<Score> result_scores;
vector<Score> sync_scores;
init_up_down (wav_data);
vector<float> fft_db;
// compute multiple time-shifted fft vectors
size_t n_bands = Params::max_band - Params::min_band + 1;
for (size_t sync_shift = 0; sync_shift < Params::frame_size; sync_shift += Params::sync_search_step)
{
sync_fft (wav_data, sync_shift, frame_count (wav_data) - 1, fft_db);
for (int start_frame = 0; start_frame < frame_count (wav_data); start_frame++)
{
const size_t sync_index = start_frame * Params::frame_size + sync_shift;
if ((start_frame + mark_sync_frame_count()) * wav_data.n_channels() * n_bands < fft_db.size())
{
double quality = sync_decode (wav_data, start_frame, fft_db);
// printf ("%zd %f\n", sync_index, quality);
sync_scores.emplace_back (Score { sync_index, quality });
}
}
}
sort (sync_scores.begin(), sync_scores.end(), [] (const Score& a, const Score &b) { return a.index < b.index; });
for (size_t i = 0; i < sync_scores.size(); i++)
{
// printf ("%zd %f\n", sync_scores[i].index, sync_scores[i].quality);
if (sync_scores[i].quality > Params::sync_threshold1)
{
double q_last = -1;
double q_next = -1;
if (i > 0)
q_last = sync_scores[i - 1].quality;
if (i + 1 < sync_scores.size())
q_next = sync_scores[i + 1].quality;
if (sync_scores[i].quality > q_last && sync_scores[i].quality > q_next)
{
//printf ("%zd %s %f", sync_scores[i].index, find_closest_sync (sync_scores[i].index), sync_scores[i].quality);
// refine match
double best_quality = sync_scores[i].quality;
size_t best_index = sync_scores[i].index;
int start = std::max (int (sync_scores[i].index) - Params::sync_search_step, 0);
int end = sync_scores[i].index + Params::sync_search_step;
for (int fine_index = start; fine_index <= end; fine_index += Params::sync_search_fine)
{
sync_fft (wav_data, fine_index, mark_sync_frame_count(), fft_db);
if (fft_db.size())
{
double q = sync_decode (wav_data, 0, fft_db);
if (q > best_quality)
{
best_quality = q;
best_index = fine_index;
}
}
}
//printf (" => refined: %zd %s %f\n", best_index, find_closest_sync (best_index), best_quality);
if (best_quality > Params::sync_threshold2)
result_scores.push_back (Score { best_index, best_quality });
}
}
}
return result_scores;
}
private:
void
sync_fft (const WavData& wav_data, size_t index, size_t count, vector<float>& fft_out_db)
{
fft_out_db.clear();
/* computing db-magnitude is expensive, so we better do it here */
for (const vector<complex<float>>& spect : compute_frame_ffts (wav_data, index, count))
{
const double min_db = -96;
for (int i = Params::min_band; i <= Params::max_band; i++)
fft_out_db.push_back (db_from_factor (abs (spect[i]), min_db));
}
}
const char*
find_closest_sync (size_t index)
{
int best_error = 0xffff;
int best = 0;
for (int i = 0; i < 100; i++)
{
int error = abs (int (index) - int (i * Params::sync_bits * Params::sync_frames_per_bit * Params::frame_size));
if (error < best_error)
{
best = i;
best_error = error;
}
}
static char buffer[1024]; // this code is for debugging only, so this should be ok
sprintf (buffer, "n:%d offset:%d", best, int (index) - int (best * Params::sync_bits * Params::sync_frames_per_bit * Params::frame_size));
return buffer;
}
};
int
decode_and_report (const WavData& wav_data, const string& orig_pattern, vector<vector<complex<float>>>& fft_out, vector<vector<complex<float>>>& fft_orig_out)
decode_and_report (const WavData& wav_data, const string& orig_pattern)
{
vector<float> soft_bit_vec;
if (Params::mix)
{
soft_bit_vec = mix_decode (wav_data, fft_out, fft_orig_out);
}
else
int match_count = 0, total_count = 0, sync_match = 0;
SyncFinder sync_finder;
vector<SyncFinder::Score> sync_scores = sync_finder.search (wav_data);
auto decode_single = [&] (const vector<float>& raw_bit_vec, SyncFinder::Score sync_score)
{
assert (raw_bit_vec.size() == conv_code_size (Params::payload_size));
vector<float> soft_bit_vec = normalize_soft_bits (raw_bit_vec);
float decode_error = 0;
vector<int> bit_vec = conv_decode_soft (randomize_bit_order (soft_bit_vec, /* encode */ false), &decode_error);
if (sync_score.index)
{
const int seconds = lrint (sync_score.index / wav_data.mix_freq());
printf ("pattern %2d:%02d %s %.3f %.3f\n", seconds / 60, seconds % 60, bit_vec_to_str (bit_vec).c_str(), sync_score.quality, decode_error);
}
else /* this is the combined pattern "all" */
{
printf ("pattern all %s %.3f %.3f\n", bit_vec_to_str (bit_vec).c_str(), sync_score.quality, decode_error);
}
if (!orig_pattern.empty())
{
bool match = true;
vector<int> orig_vec = bit_str_to_vec (orig_pattern);
for (size_t i = 0; i < bit_vec.size(); i++)
match = match && (bit_vec[i] == orig_vec[i % orig_vec.size()]);
if (match)
match_count++;
}
total_count++;
};
vector<float> raw_bit_vec_all (conv_code_size (Params::payload_size));
SyncFinder::Score score_all { 0, 0 };
for (auto sync_score : sync_scores)
{
soft_bit_vec = linear_decode (wav_data, fft_out, fft_orig_out);
const size_t count = mark_data_frame_count();
const size_t index = sync_score.index + (mark_sync_frame_count() * Params::frame_size);
auto fft_range_out = compute_frame_ffts (wav_data, index, count);
if (fft_range_out.size())
{
vector<vector<complex<float>>> junk;
vector<float> raw_bit_vec;
if (Params::mix)
{
raw_bit_vec = mix_decode (fft_range_out, junk, wav_data.n_channels());
}
else
{
raw_bit_vec = linear_decode (fft_range_out, junk, wav_data.n_channels());
}
decode_single (raw_bit_vec, sync_score);
score_all.quality += sync_score.quality;
for (size_t i = 0; i < raw_bit_vec_all.size(); i++)
raw_bit_vec_all[i] += raw_bit_vec[i];
}
}
if (soft_bit_vec.size() < conv_code_size (Params::payload_size))
if (total_count > 1) /* all pattern: average soft bits of all watermarks and decode */
{
fprintf (stderr, "audiowmark: input file too short to retrieve watermark\n");
fprintf (stderr, " - number of recovered raw bits %zd\n", soft_bit_vec.size());
fprintf (stderr, " - need at least %zd raw bits to get watermark\n", conv_code_size (Params::payload_size));
return 1;
score_all.quality /= total_count;
decode_single (raw_bit_vec_all, score_all);
}
/* truncate to the required length */
soft_bit_vec.resize (conv_code_size (Params::payload_size));
vector<int> bit_vec = conv_decode_soft (randomize_bit_order (soft_bit_vec, /* encode */ false));
printf ("pattern %s\n", bit_vec_to_str (bit_vec).c_str());
if (!orig_pattern.empty())
{
int bits = 0, bit_errors = 0;
printf ("match_count %d %d\n", match_count, total_count);
/* search sync markers at typical positions */
const int expect0 = Params::frames_pad_start * Params::frame_size;
const int expect_step = (mark_sync_frame_count() + mark_data_frame_count()) * Params::frame_size;
const int expect_end = frame_count (wav_data) * Params::frame_size;
vector<int> orig_vec = bit_str_to_vec (orig_pattern);
for (size_t i = 0; i < bit_vec.size(); i++)
for (int expect_index = expect0; expect_index + expect_step < expect_end; expect_index += expect_step)
{
bits++;
if (bit_vec[i] != orig_vec[i % orig_vec.size()])
bit_errors++;
for (auto sync_score : sync_scores)
{
if (abs (int (sync_score.index + Params::test_cut) - expect_index) < Params::frame_size / 2)
{
sync_match++;
break;
}
}
}
printf ("bit_error_raw %d %d\n", bit_errors, bits);
printf ("bit_error_rate %.5f %%\n", double (100.0 * bit_errors) / bits);
printf ("sync_match %d %zd\n", sync_match, sync_scores.size());
}
return 0;
}
......@@ -741,14 +1073,7 @@ get_watermark (const string& infile, const string& orig_pattern)
fprintf (stderr, "audiowmark: error loading %s: %s\n", infile.c_str(), wav_data.error_blurb());
return 1;
}
// to keep the watermark detection code simpler, we truncate samples to avoid partial filled blocks
truncate_to_block_size (wav_data);
vector<vector<complex<float>>> fft_out = compute_frame_ffts (wav_data);
vector<vector<complex<float>>> fft_orig_out; /* no original data -> blind decode */
return decode_and_report (wav_data, orig_pattern, fft_out, fft_orig_out);
return decode_and_report (wav_data, orig_pattern);
}
int
......@@ -768,14 +1093,15 @@ get_watermark_delta (const string& origfile, const string& infile, const string&
return 1;
}
// to keep the watermark detection code simpler, we truncate samples to avoid partial filled blocks
truncate_to_block_size (wav_data);
truncate_to_block_size (orig_wav_data);
/*
vector<vector<complex<float>>> fft_out = compute_frame_ffts (wav_data);
vector<vector<complex<float>>> fft_orig_out = compute_frame_ffts (orig_wav_data);
return decode_and_report (wav_data, orig_pattern, fft_out, fft_orig_out);
*/
/* FIXME? */
printf ("delta decoding currently not supported\n");
return 1;
}
int
......@@ -792,10 +1118,10 @@ gentest (const string& infile, const string& outfile)
const vector<float>& in_signal = wav_data.samples();
vector<float> out_signal;
/* 42 seconds of audio - starting at 30 seconds of the original track */
/* this is approximately the minimal amount of audio data required for storing a 128-bit encoded message */
const size_t offset = 30 * wav_data.n_channels() * int (wav_data.mix_freq());
const size_t n_samples = 42 * wav_data.n_channels() * int (wav_data.mix_freq());
/* 160 seconds of audio - this is approximately the minimal amount of audio data required
* for storing three separate watermarks with a 128-bit encoded message */
const size_t offset = 0 * wav_data.n_channels() * int (wav_data.mix_freq());
const size_t n_samples = 165 * wav_data.n_channels() * int (wav_data.mix_freq());
if (in_signal.size() < (offset + n_samples))
{
fprintf (stderr, "audiowmark: input file %s too short\n", infile.c_str());
......@@ -838,6 +1164,32 @@ scale (const string& infile, const string& outfile)
return 0;
}
int
cut_start (const string& infile, const string& outfile, const string& start_str)
{
WavData wav_data;
if (!wav_data.load (infile))
{
fprintf (stderr, "audiowmark: error loading %s: %s\n", infile.c_str(), wav_data.error_blurb());
return 1;
}
size_t start = atoi (start_str.c_str());
const vector<float>& in_signal = wav_data.samples();
vector<float> out_signal;
for (size_t i = start * wav_data.n_channels(); i < in_signal.size(); i++)
out_signal.push_back (in_signal[i]);
WavData out_wav_data (out_signal, wav_data.n_channels(), wav_data.mix_freq(), wav_data.bit_depth());
if (!out_wav_data.save (outfile))
{
fprintf (stderr, "audiowmark: error saving %s: %s\n", outfile.c_str(), out_wav_data.error_blurb());
return 1;
}
return 0;
}
int
get_snr (const string& origfile, const string& wmfile)
{
......@@ -929,6 +1281,10 @@ main (int argc, char **argv)
{
scale (argv[2], argv[3]);
}
else if (op == "cut-start" && argc == 5)
{
cut_start (argv[2], argv[3], argv[4]);
}
else if (op == "get-delta" && argc == 4)
{
return get_watermark_delta (argv[2], argv[3], /* no ber */ "");
......
......@@ -8,7 +8,7 @@ if [ "x$AWM_SEEDS" == "x" ]; then
AWM_SEEDS=0
fi
if [ "x$AWM_REPORT" == "x" ]; then
AWM_REPORT=ber
AWM_REPORT=fer
fi
if [ "x$AWM_FILE" == "x" ]; then
AWM_FILE=t
......@@ -21,6 +21,8 @@ fi
cat test_list
elif [ "x$AWM_SET" == "xhuge" ]; then
ls huge/T*
elif [ "x$AWM_SET" == "xhuge2" ]; then
ls huge2/T*
else
echo "bad AWM_SET $AWM_SET" >&2
exit 1
......@@ -45,6 +47,13 @@ do
fi
audiowmark add "$i" ${AWM_FILE}.wav $PATTERN $AWM_PARAMS --test-key $SEED >/dev/null
if [ "x$AWM_RAND_CUT" != x ]; then
CUT=$RANDOM
audiowmark cut-start "${AWM_FILE}.wav" "${AWM_FILE}.wav" $CUT
TEST_CUT_ARGS="--test-cut $CUT"
else
TEST_CUT_ARGS=""
fi
if [ "x$TRANSFORM" == "xmp3" ]; then
if [ "x$2" == "x" ]; then
echo "need mp3 bitrate" >&2
......@@ -93,15 +102,17 @@ do
exit 1
fi
# blind decoding
audiowmark cmp ${AWM_FILE}.wav $PATTERN $AWM_PARAMS --test-key $SEED
audiowmark cmp ${AWM_FILE}.wav $PATTERN $AWM_PARAMS --test-key $SEED $TEST_CUT_ARGS
# decoding with original
# audiowmark cmp-delta "$i" t.wav $PATTERN $AWM_PARAMS --test-key $SEED
done
done | grep bit_error_rate | {
if [ "x$AWM_REPORT" == "xber" ]; then
awk 'BEGIN { max_er = er = n = 0 } { er += $2; n++; if ($2 > max_er) max_er = $2;} END { print er / n, max_er; }'
elif [ "x$AWM_REPORT" == "xfer" ]; then
awk 'BEGIN { bad = n = 0 } { if ($2 > 0) bad++; n++; } END { print bad, n, bad * 100.0 / n; }'
done | {
if [ "x$AWM_REPORT" == "xfer" ]; then
awk 'BEGIN { bad = n = 0 } $1 == "match_count" { if ($2 == 0) bad++; n++; } END { print bad, n, bad * 100.0 / n; }'
elif [ "x$AWM_REPORT" == "xsync" ]; then
awk 'BEGIN { bad = n = 0 } $1 == "sync_match" { bad += (3 - $2) / 3.0; n++; } END { print bad, n, bad * 100.0 / n; }'
elif [ "x$AWM_REPORT" == "xsyncv" ]; then
awk '{ print "###", $0; } $1 == "sync_match" { correct += $2; missing += 3 - $2; incorrect += $3-$2; print "correct:", correct, "missing:", missing, "incorrect:", incorrect; }'
else
echo "unknown report $AWM_REPORT" >&2
exit 1
......
#include "utils.hh"
#include "convcode.hh"
#include <array>
#include <algorithm>
......@@ -74,7 +75,7 @@ conv_encode (const vector<int>& in_bits)
/* decode using viterbi algorithm */
vector<int>
conv_decode_soft (const vector<float>& coded_bits)
conv_decode_soft (const vector<float>& coded_bits, float *error_out)
{
vector<int> decoded_bits;
......@@ -141,6 +142,8 @@ conv_decode_soft (const vector<float>& coded_bits)
}
unsigned int state = 0;
if (error_out)
*error_out = error_count.back()[state].delta / coded_bits.size();
for (size_t idx = error_count.size() - 1; idx > 0; idx--)
{
decoded_bits.push_back (error_count[idx][state].bit);
......
......@@ -7,6 +7,6 @@
size_t conv_code_size (size_t msg_size);
std::vector<int> conv_encode (const std::vector<int>& in_bits);
std::vector<int> conv_decode_hard (const std::vector<int>& coded_bits);
std::vector<int> conv_decode_soft (const std::vector<float>& coded_bits);
std::vector<int> conv_decode_soft (const std::vector<float>& coded_bits, float *error_out = nullptr);
#endif /* AUDIOWMARK_CONV_CODE_HH */
......@@ -2,8 +2,11 @@
#include <fftw3.h>
#include <map>
using std::vector;
using std::complex;
using std::map;
float *
new_array_float (size_t N)
......@@ -22,8 +25,9 @@ free_array_float (float *f)
void
fftar_float (size_t N, float *in, float *out)
{
static fftwf_plan plan = nullptr; // FIXME: should be one plan per fft size
static map<int, fftwf_plan> plan_for_size;
fftwf_plan& plan = plan_for_size[N];
if (!plan)
{
float *plan_in = new_array_float (N);
......@@ -38,8 +42,9 @@ fftar_float (size_t N, float *in, float *out)
void
fftsr_float (size_t N, float *in, float *out)
{
static fftwf_plan plan = nullptr; // FIXME: should be one plan per fft size
static map<int, fftwf_plan> plan_for_size;
fftwf_plan& plan = plan_for_size[N];
if (!plan)
{
float *plan_in = new_array_float (N);
......
......@@ -4,7 +4,14 @@
#include <complex>
#include <vector>
/* high level api */
std::vector<std::complex<float>> fft (const std::vector<float>& in);
std::vector<float> ifft (const std::vector<std::complex<float>>& in);
/* more efficient: low level api */
void fftar_float (size_t N, float *in, float *out);
float *new_array_float (size_t N);
void free_array_float (float *f);
#endif /* AUDIOWMARK_FFT_HH */
#!/bin/bash
echo ".sync-codec-resistence"
echo '[frame="topbot",options="header",cols="<2,6*>1"]'
echo '|=========================='
echo -n "| "
for D in $(seq 10 -1 5)
do
DELTA=$(printf "0.0%02d\n" $D)
echo -n "| $DELTA"
done
echo
for TEST in mp3 double-mp3 ogg
do
if [ $TEST == mp3 ]; then
echo -n "| mp3 128kbit/s"
elif [ $TEST == double-mp3 ]; then
echo -n "| double mp3 128kbit/s"
elif [ $TEST == ogg ]; then
echo -n "| ogg 128kbit/s"
else
echo "error: bad TEST $TEST ???"
exit 1
fi
for D in $(seq 10 -1 5)
do
DELTA=$(printf "0.0%02d\n" $D)
cat $DELTA-$TEST-* | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}'
done
echo
done
echo
echo '|=========================='
#!/bin/bash
DELTA_RANGE="0.005 0.006 0.007 0.008 0.009 0.010"
SEEDS="$(seq 0 19)"
echo -n "all: "
for SEED in $SEEDS
do
for DELTA in $DELTA_RANGE
do
echo -n "$DELTA-ogg-$SEED $DELTA-mp3-$SEED $DELTA-double-mp3-$SEED "
done
done
echo
echo
for SEED in $SEEDS
do
for DELTA in $DELTA_RANGE
do
FILE="$DELTA-ogg-$SEED"
echo "$FILE:"
echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--water-delta $DELTA' AWM_REPORT=fer AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh ogg 128 ) >x$FILE"
echo -e "\tmv x$FILE $FILE"
echo
FILE="$DELTA-mp3-$SEED"
echo "$FILE:"
echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--water-delta $DELTA' AWM_REPORT=fer AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE"
echo -e "\tmv x$FILE $FILE"
echo
FILE="$DELTA-double-mp3-$SEED"
echo "$FILE:"
echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--water-delta $DELTA' AWM_REPORT=fer AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh double-mp3 128 ) >x$FILE"
echo -e "\tmv x$FILE $FILE"
echo
done
done
......@@ -5,6 +5,6 @@ mkdir -p test
seq=1
cat test_list | while read f
do
audiowmark gentest "$f" test/T$(printf "%02d__%s" $seq $(echo $f | sed 's, ,_,g;s,.*/,,g')).wav
audiowmark gentest "$f" test/T$(printf "%02d__%s" $seq $(echo $f | sed 's, ,_,g;s,.*/,,g')).wav || exit 1
((seq++))
done
......@@ -42,6 +42,7 @@ uint64_from_buffer (unsigned char *buffer)
+ buffer[7];
}
#if 0 /* debugging only */
static void
print (const string& label, const vector<unsigned char>& data)
{
......@@ -50,6 +51,7 @@ print (const string& label, const vector<unsigned char>& data)
printf ("%02x ", ch);
printf ("\n");
}
#endif
Random::Random (uint64_t seed, Stream stream)
{
......
......@@ -11,9 +11,11 @@ class Random
{
public:
enum class Stream {
up_down = 1,
mix = 2,
bit_order = 3
data_up_down = 1,
sync_up_down = 2,
pad_up_down = 3,
mix = 4,
bit_order = 5
};
private:
gcry_cipher_hd_t aes_ctr_cipher;
......
# pseudo random pattern
PATTERN=4e1243bd22c66e76c2ba9eddc1f91394e57f9f83
PATTERN=4e1243bd22c66e76c2ba9eddc1f91394
for i in test/T*
do
......
#!/bin/bash
SEEDS="$1"
MAX_SEED=$(($SEEDS - 1))
P="$2"
shift 2
echo "n seeds : $SEEDS"
echo "ber-test args : $@"
echo "params : $P"
for seed in $(seq 0 $MAX_SEED)
do
echo $(AWM_SEEDS=$seed AWM_PARAMS="$P" AWM_REPORT="sync" ber-test.sh "$@")
done | awk '{bad += $1; files += $2; print bad, files, bad * 100. / files }'
......@@ -3,7 +3,6 @@
/home/stefan/files/music/artists/gotye/making_mirrors/10 - Giving Me A Chance.flac
/home/stefan/files/music/artists/alice_cooper/the_definitive_alice_cooper/18_how_you_gonna_see_me_now.flac
/home/stefan/files/music/artists/mendelssohn/The Hebrides, Symphonies Nos.1, 4/02 - Symphony No. 1 C minor Op. 11 - Allegro di molto.flac
/home/stefan/files/music/artists/daft_punk/tron_legacy/15 - Solar Sailer.flac
/home/stefan/files/music/artists/depeche_mode/the_singles_81_to_85/09 - Love In Itself.flac
/home/stefan/files/music/artists/beethoven/Klavierkonzerte Nrr.3 & 4 (CD 2)/02 - Klavierkonzert Nr. 3 C-moll - 2. Largo.flac
/home/stefan/files/music/artists/joe_henderson/page_one/05 - Jinrikisha.flac
......@@ -12,7 +11,7 @@
/home/stefan/files/music/artists/mixed/the_world_of_trance_2/cd1/10 - Nature One - The Sense Of Live (Hurricanmix).flac
/home/stefan/files/music/artists/charles_mingus/Mingus Plays Piano/08 - Meditations for Moses.flac
/home/stefan/files/music/artists/lena/good_news/06 - Mama Told Me.flac
/home/stefan/files/music/artists/scriabin/Horowitz Plays Scriabin/06 - Scriabin - Prelude, Op.11, No.16.flac
/home/stefan/files/music/artists/mixed/tunnel_trance_force/43_cd1/24 - Niosecontrollers - Crump.flac
/home/stefan/files/music/artists/paniq/beyond_good_and_evil/paniq - Beyond Good and Evil - 02 Tartaros (The Barren Acres of Open Source).flac
/home/stefan/files/music/artists/mixed/Katia Marielle Labeque - Rhapsody in Blue/05 - Strawinsky: Petrushka_Volksfest waehrend der Fastnacht.flac
/home/stefan/files/music/artists/rosenstolz/mondkuss_cd1/09_die_zigarette_danach.flac
......@@ -23,3 +22,4 @@
/home/stefan/files/music/artists/mixed/Katia Marielle Labeque - Rhapsody in Blue/01 - Gershwin: Rhapsody in Blue.flac
/home/stefan/files/music/artists/loreena_mckennitt/collection/04 - Loreena Mckennitt - Caravanserai.flac
/home/stefan/files/music/artists/mumford_and_sons/sigh_no_more/05 - White Blank Page.flac
/home/stefan/files/music/artists/daft_punk/tron_legacy/22 - Finale.flac
......@@ -185,36 +185,18 @@ WavData::mix_freq() const
return m_mix_freq;
}
int
WavData::n_channels() const
{
return m_n_channels;
}
int
WavData::bit_depth() const
{
return m_bit_depth;
}
const vector<float>&
WavData::samples() const
{
return m_samples;
}
void
WavData::set_samples (const vector<float>& samples)
{
m_samples = samples;
}
size_t
WavData::n_values() const
{
return m_samples.size();
}
const char *
WavData::error_blurb() const
{
......
......@@ -20,12 +20,25 @@ public:
bool save (const std::string& filename);
float mix_freq() const;
int n_channels() const;
size_t n_values() const;
int bit_depth() const;
const std::vector<float>& samples() const;
const char *error_blurb() const;
int
n_channels() const
{
return m_n_channels;
}
size_t
n_values() const
{
return m_samples.size();
}
const std::vector<float>&
samples() const
{
return m_samples;
}
void set_samples (const std::vector<float>& samples);
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment