Beatmup
signal.cpp
Go to the documentation of this file.
1 /*
2  Beatmup image and signal processing library
3  Copyright (C) 2019, lnstadrum
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "signal.h"
20 #include "signal_fragment.h"
21 #include "processing.h"
22 #include "wav_utilities.h"
23 #include "../utils/input_stream.h"
24 #include "../exception.h"
25 #include <algorithm>
26 
27 using namespace Beatmup;
28 using namespace Audio;
29 
30 
33 }
34 
35 
36 Signal::Signal(Context& context, AudioSampleFormat format, int sampleRate, unsigned char channels, float defaultFragmentLenSec) :
37  ctx(context),
38  format(format),
39  defaultFragmentSize(floorf_fast(defaultFragmentLenSec * sampleRate)),
40  sampleRate(sampleRate),
41  channelCount(channels)
42 {
44 }
45 
46 
47 void Signal::insert(const Signal& signal, dtime time) {
48  if (signal.channelCount != channelCount || signal.format != format)
49  throw Signal::IncompatibleFormat(signal, *this);
50  Sequence::insert(signal, time);
51 }
52 
53 
54 void Signal::reserve(dtime length) {
55  int curDur;
56  while ((curDur = getDuration()) < length) {
58  newbie->zero();
59  concatenate(*newbie, 0, std::min(defaultFragmentSize, length - curDur));
60  }
61 }
62 
63 
65  // open file
66  FileInputStream stream(filename);
67  if (!stream.isOpen())
68  throw IOError(filename, "Unable to open for reading");
69  return Signal::loadWAV(ctx, stream);
70 }
71 
73  // read header
74  WAV::Header header;
75  stream(&header, sizeof(header));
77 
78  // get sample format
80  switch (header.bitsPerSample) {
81  case 8:
82  format = Int8;
83  break;
84  case 16:
85  format = Int16;
86  break;
87  case 32:
88  format = Int32;
89  break;
90  default:
91  throw WAV::InvalidWavFile("Incorrect WAV file: unsupported BPS");
92  }
93 
94  // check number of channels (insanity check, basically)
95  if (header.numChannels <= 0 || header.numChannels > 255)
96  throw WAV::InvalidWavFile("Incorrect WAV file: unsupported channel count");
97 
98  // create signal
99  Signal* signal = new Signal(ctx, format, header.sampleRate, header.numChannels);
100  dtime totalTime = header.dataSizeBytes / (header.numChannels * header.bitsPerSample / 8);
101  signal->reserve(totalTime);
102 
103  // read data using pointer
104  Writer pointer(*signal, 0);
105  while (pointer.hasData() && !stream.eof()) {
106  void* data;
107  dtime length = pointer.acquireBuffer(data);
108  stream(data, length * header.numChannels * AUDIO_SAMPLE_SIZE[format]);
109  pointer.releaseBuffer();
110  pointer.jump(length);
111  }
112 
113  return signal;
114 }
115 
116 
117 void Signal::saveWAV(const char* filename) {
118  // open file
119  std::ofstream file(filename, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
120  if (!file.is_open())
121  throw IOError(filename, "Unable to open for writing");
122 
123  // init and write header
124  unsigned char sampleSize = AUDIO_SAMPLE_SIZE[format];
125  WAV::Header header;
126  header.set(sampleRate, sampleSize * 8, channelCount, getDuration() * channelCount * sampleSize);
127  file.write((char*)&header, sizeof(header));
128 
129  // write out
130  Reader pointer(*this, 0);
131  while (pointer.hasData() && !file.eof()) {
132  if (file.fail())
133  throw IOError(filename, "Failed while writing");
134  const void* data;
135  dtime length = pointer.acquireBuffer(data);
136  file.write((const char*)data, length * channelCount * sampleSize);
137  pointer.releaseBuffer();
138  pointer.jump(length);
139  }
140 }
141 
142 
143 Signal::Pointer::Pointer(Signal& signal, dtime time, bool writing): Sequence::Pointer(signal, time, writing)
144 {}
145 
146 
148 }
149 
150 
151 unsigned char Signal::Pointer::getChannelCount() const {
152  return ((SignalFragment*)pointer.fragment)->getChannelCount();
153 }
154 
155 
156 Signal::Reader::Reader(Signal& signal, dtime time) : Pointer(signal, time, false) {};
157 
158 
160  if (pointer.isNull()) {
161  data = nullptr;
162  return 0;
163  }
164  SignalFragment* fragment = (SignalFragment*)pointer.fragment;
165  data = fragment->getData() + pointer.offset * fragment->getBlockSize();
166  return pointer.length;
167 }
168 
169 
170 Signal::Writer::Writer(Signal& signal, dtime time) : Pointer(signal, time, true) {};
171 
173  if (pointer.isNull()) {
174  data = nullptr;
175  return 0;
176  }
177  SignalFragment* fragment = (SignalFragment*)pointer.fragment;
178  data = fragment->getData() + pointer.offset * fragment->getBlockSize();
179  return pointer.length;
180 }
181 
182 
183 void Signal::Meter::prepare(Signal& signal, int skipOnStart, int numSteps) {
184  Meter me(signal, 0, MeasuringMode::approximateUsingLookup);
185  for (int skip = 0; skip < skipOnStart; skip++)
186  me.step();
187  while (me.hasData()) {
188  SignalFragment* fragment = (SignalFragment*)me.pointer.fragment;
189  if (fragment && !fragment->isDynamicsLookupAvailable())
190  fragment->updateDynamicsLookup();
191  for (int skip = 0; skip < numSteps; skip++)
192  me.step();
193  }
194 }
195 
196 
198  Pointer(signal, time, false), mode(mode)
199 {}
200 
201 
202 void Signal::Meter::prepareSignal(Signal& signal, bool runTask) {
203  if (runTask) {
204 
205  class DynamicsLookupPreparation : public AbstractTask {
206  public:
207  Signal& signal;
208  bool process(TaskThread& thread) {
209  // interleaving fragments among threads
210  Meter::prepare(signal, thread.currentThread(), thread.numThreads());
211  return true;
212  }
213 
214  TaskDeviceRequirement getExecutionMode() const { return AbstractTask::TaskDeviceRequirement::CPU_ONLY; }
215  ThreadIndex getMaxThreads() const { return MAX_THREAD_INDEX; }
216  DynamicsLookupPreparation(Signal& signal) : signal(signal) {}
217  };
218 
219  DynamicsLookupPreparation task(signal);
220  signal.getContext().performTask(task);
221  }
222  else
223  Meter::prepare(signal);
224 }
225 
226 
227 template<typename sample> void Signal::Meter::measureInFragments(dtime len, int resolution, sample* min, sample* max) {
228  const int channelCount = ((SignalFragment*)pointer.fragment)->getChannelCount();
229  const dtime startTime = getTime();
230  dtime t1 = startTime;
231  sample *pMin = min, *pMax = max;
232 
233  // for all bins
234  for (int bin = 1; bin <= resolution; bin++) {
235  // determining bin bounds
236  dtime t0 = t1;
237  t1 = startTime + (long long)len * bin / resolution;
238 
239  // determining start time within fragment
240  dtime fragStart = t0 - getTime() + pointer.offset;
241 
242  // initializing min and max values
243  for (int ch = 0; ch < channelCount; ch++) {
244  pMin[ch].x = sample::MAX_VALUE;
245  pMax[ch].x = sample::MIN_VALUE;
246  }
247 
248  // scanning fragments
249  while (getTime() + pointer.length < t1) {
250  ((SignalFragment*)pointer.fragment)->measureDynamics(fragStart, pointer.offset + pointer.length, pMin, pMax, mode);
251  step();
252  fragStart = pointer.offset;
253  }
254  ((SignalFragment*)pointer.fragment)->measureDynamics(fragStart, pointer.offset + t1 - getTime(), pMin, pMax, mode);
255 
256  // if no values defined
257  for (int ch = 0; ch < channelCount; ch++)
258  if (pMax[ch] < pMin[ch])
259  pMin[ch].x = pMax[ch].x = 0;
260  pMin += channelCount;
261  pMax += channelCount;
262  }
263 }
264 
265 template void Signal::Meter::measureInFragments(dtime len, int resolution, sample8* min, sample8* max);
266 template void Signal::Meter::measureInFragments(dtime len, int resolution, sample16* min, sample16* max);
267 template void Signal::Meter::measureInFragments(dtime len, int resolution, sample32* min, sample32* max);
268 template void Signal::Meter::measureInFragments(dtime len, int resolution, sample32f* min, sample32f* max);
269 
270 
271 namespace Beatmup { // why? Because of a bug in gcc
272 
273  template<> void Signal::Meter::measure(dtime len, int resolution, sample16 min[], sample16 max[]) {
274  const AudioSampleFormat fmt = ((SignalFragment*)pointer.fragment)->getAudioFormat();
275  if (fmt == Int16) {
276  measureInFragments(len, resolution, (sample16*)min, (sample16*)max);
277  return;
278  }
279  // converting min/max types
280  const int size = resolution * ((SignalFragment*)pointer.fragment)->getChannelCount();
281  switch (fmt) {
282  case Int8: {
283  sample8 *tmin = new sample8[size], *tmax = new sample8[size];
284  measureInFragments(len, resolution, tmin, tmax);
285  convertSamples<sample8, sample16>(tmin, min, resolution);
286  convertSamples<sample8, sample16>(tmax, max, resolution);
287  delete[] tmin;
288  delete[] tmax;
289  break;
290  }
291  case Int32: {
292  sample32 *tmin = new sample32[size], *tmax = new sample32[size];
293  measureInFragments(len, resolution, tmin, tmax);
294  convertSamples<sample32, sample16>(tmin, min, resolution);
295  convertSamples<sample32, sample16>(tmax, max, resolution);
296  delete[] tmin;
297  delete[] tmax;
298  break;
299  }
300  case Float32: {
301  sample32f *tmin = new sample32f[size], *tmax = new sample32f[size];
302  measureInFragments(len, resolution, tmin, tmax);
303  convertSamples<sample32f, sample16>(tmin, min, resolution);
304  convertSamples<sample32f, sample16>(tmax, max, resolution);
305  delete[] tmin;
306  delete[] tmax;
307  break;
308  }
309  default:
310  Insanity::insanity("Unknown audio format");
311  }
312  }
313 }
314 
315 
316 namespace Kernels {
317  template<typename in_t, typename out_t> class RenderAudio {
318  public:
319  /**
320  Renders a continuous audio signal from Signal::Reader
321  */
322  static void process(const in_t* whatever, out_t* output, Signal::Reader& ptr, dtime length, const int inCh, const int outCh) {
323  static const out_t _0{ 0 };
324  const int numc = (int)std::min(inCh, outCh);
325  while (length > 0 && ptr.hasData()) {
326  const void* data;
327  int chunk = ptr.acquireBuffer(data);
328  if (chunk > length)
329  chunk = length;
330 
331  const in_t* input = (const in_t*)data;
332  for (int t = 0; t < chunk; ++t, input += inCh, output += outCh) {
333  int c = 0;
334  for (; c < numc; ++c)
335  output[c] = input[c];
336  for (; c < outCh; ++c)
337  output[c] = _0;
338  }
339 
340  ptr.releaseBuffer();
341  ptr.jump(chunk);
342  length -= chunk;
343  }
344 
345  memset(output, 0, length * outCh);
346  }
347  };
348 }
349 
350 
351 Signal::Source::Source(Signal& signal): signal(&signal) {}
352 
353 
355  const dtime sampleRate,
356  const AudioSampleFormat sampleFormat,
357  const unsigned char numChannels,
358  const dtime maxBufferLen
359 ) {
360  this->numChannels = numChannels;
361  this->sampleFormat = sampleFormat;
362 }
363 
364 
366  this->time = time;
367 }
368 
369 
371  TaskThread& thread,
372  sample8* buffer,
373  const dtime bufferLength
374 ) {
375  Reader ptr(*signal, time);
376  Processing::pipeline<Kernels::RenderAudio>(signal->getSampleFormat(), sampleFormat, nullptr, buffer, ptr, bufferLength, signal->getChannelCount(), numChannels);
377  time += bufferLength;
378 }
379 
380 
382  Exception(
383  "Incompatible format: %s @ %d channels vs <-> %s @ %d channels",
386  ),
387  sourceFormat(source.getSampleFormat()), destFormat(dest.getSampleFormat()), sourceChannelCount(source.getChannelCount()), destChannelCount(dest.getChannelCount())
388 {}
Task: an operation that can be executed by multiple threads in parallel.
Definition: parallelism.h:90
@ CPU_ONLY
this task does not use GPU
Communicates an error when inserting a incompatible fragment into a Signal.
Definition: signal.h:177
IncompatibleFormat(const Signal &source, const Signal &dest)
Definition: signal.cpp:381
Signal dynamics meter.
Definition: signal.h:71
void measureInFragments(dtime len, int resolution, sample *min, sample *max)
Measures signal dynamics in a given period of time.
Definition: signal.cpp:227
static void prepareSignal(Signal &signal, bool runTask=true)
Precomputes the dynamics lookup all over the signal, where needed.
Definition: signal.cpp:202
void measure(dtime len, int resolution, sample min[], sample max[])
Measures signal dynamics in a given period of time.
Meter(Signal &signal, dtime time, MeasuringMode mode=MeasuringMode::approximateUsingLookup)
Constructs a new meter.
Definition: signal.cpp:197
MeasuringMode
Specifies how to compute signal dynamics (minima and maxima in a given period of time)
Definition: signal.h:76
static void prepare(Signal &signal, int skipOnStart=0, int numSteps=1)
Definition: signal.cpp:183
Implements a Sequence::Pointer for audio signals.
Definition: signal.h:41
unsigned char getChannelCount() const
Definition: signal.cpp:151
virtual void releaseBuffer()
Definition: signal.cpp:147
Pointer(Signal &signal, dtime time, bool writing)
Definition: signal.cpp:143
Provides reading access to the signal.
Definition: signal.h:53
Reader(Signal &signal, dtime time)
Definition: signal.cpp:156
dtime acquireBuffer(const void *&data)
Definition: signal.cpp:159
void prepare(const dtime sampleRate, const AudioSampleFormat sampleFormat, const unsigned char numChannels, const dtime maxBufferLen)
Prepares a source to render audio data.
Definition: signal.cpp:354
void render(TaskThread &thread, sample8 *buffer, const dtime bufferLength)
Renders audio data to the target output buffer given by the user.
Definition: signal.cpp:370
void setClock(dtime time)
Called by the source user when an abrupt time change occurs (e.g., due to seeking)
Definition: signal.cpp:365
Provides writing access to the signal.
Definition: signal.h:62
Writer(Signal &signal, dtime time)
Definition: signal.cpp:170
dtime acquireBuffer(void *&data)
Definition: signal.cpp:172
An audio signal.
Definition: signal.h:36
void insert(const Signal &signal, dtime time)
Inserts a Signal into the current signal at a specific time moment.
Definition: signal.cpp:47
Context & getContext() const
Definition: signal.h:242
unsigned char getChannelCount() const
Definition: signal.h:232
AudioSampleFormat format
sample format
Definition: signal.h:187
AudioSampleFormat getSampleFormat() const
Definition: signal.h:237
Signal(Context &context, AudioSampleFormat format, int sampleRate, unsigned char channels, float defaultFragmentLenSec=DEFAULT_FRAGMENT_LENGTH_SEC)
Creates an empty signal.
Definition: signal.cpp:36
unsigned char channelCount
number of channels
Definition: signal.h:191
void reserve(dtime length)
Prolongates the sequence if necessary, to ensure given length.
Definition: signal.cpp:54
static Signal * loadWAV(Context &ctx, const char *fileName)
Definition: signal.cpp:64
int sampleRate
sampling frequency
Definition: signal.h:190
void saveWAV(const char *filename)
Stores the signal to a PCM-encoded WAV file.
Definition: signal.cpp:117
virtual Signal * createEmpty() const
Initializes an empty sequence, used to bootstrap copying operations.
Definition: signal.cpp:31
const dtime defaultFragmentSize
Definition: signal.h:189
void set(uint32_t sampleRate, uint16_t bitsPerSample, uint16_t channelCount, uint32_t dataSize)
Communicates an error related to a WAV file content.
Definition: wav_utilities.h:68
static void check(Header &header)
Basic class: task and memory management, any kind of static data.
Definition: context.h:59
float performTask(AbstractTask &task, const PoolIndex pool=DEFAULT_POOL)
Performs a given task.
Definition: context.cpp:240
Base class for all exceptions.
Definition: exception.h:37
InputStream reading from a file.
Definition: input_stream.h:54
const dtime getDuration() const
Returns sequence duration in number of samples.
Definition: sequence.h:123
void concatenate(Fragment &fragment, dtime offset, dtime length)
Adds a new fragment at the end of the sequence.
Minimal input stream interface.
Definition: input_stream.h:27
virtual bool eof() const =0
Returns true, if the end of the stream is reached (i.e., all the data is read or the stream is empty)...
static void insanity(const char *message)
Definition: exception.h:136
Thread executing tasks.
Definition: parallelism.h:154
virtual ThreadIndex numThreads() const =0
ThreadIndex currentThread() const
Definition: parallelism.h:165
static void process(const in_t *whatever, out_t *output, Signal::Reader &ptr, dtime length, const int inCh, const int outCh)
Renders a continuous audio signal from Signal::Reader.
Definition: signal.cpp:322
#define BEATMUP_ASSERT_DEBUG(C)
Definition: exception.h:163
const char *const AUDIO_FORMAT_NAME[]
const int AUDIO_SAMPLE_SIZE[]
unsigned char ThreadIndex
number of threads / thread index
Definition: parallelism.h:68
static const ThreadIndex MAX_THREAD_INDEX
maximum possible thread index value
Definition: parallelism.h:71
int dtime
discrete time
Definition: basic_types.h:37
AudioSampleFormat
Format of audio samples.
@ Int8
signed integer, 8 bit per sample
@ Int32
signed integer, 32 bit per sample
@ Float32
floating point, 32 bit per sample
@ Int16
signed integer, 16 bit per sample
Operations kernels.
Definition: basic_types.h:85
CustomPoint< numeric > min(const CustomPoint< numeric > &a, const CustomPoint< numeric > &b)
Definition: geometry.h:724
CustomPoint< numeric > max(const CustomPoint< numeric > &a, const CustomPoint< numeric > &b)
Definition: geometry.h:728
int floorf_fast(float x)
Definition: utils.hpp:26
JNIEnv jobject jint jint jint channels
JNIEnv jobject jint jint jint jfloat fragment
JNIEnv jlong jint t1
JNIEnv jobject jint format
if(nativeObj)
Beatmup::CustomPipeline::TaskHolder * newbie
Beatmup::Context * ctx
JNIEnv jlong jstring filename
jlong jlong jint time
JNIEnv jlong jint out
JNIEnv jlong jint mode
jlong jobject size
jlong jint jfloat step
Beatmup::NNets::InferenceTask * task