Beatmup
sequence.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 "sequence.h"
20 #include "../exception.h"
21 #include "../debug.h"
22 #include <algorithm>
23 
24 using namespace Beatmup;
25 using namespace Beatmup::Fragments;
26 
27 #ifdef BEATMUP_DEBUG
28 void consistencyTest(const std::vector<FragmentPtr>& fragments) {
29  int index = 0;
30  for (auto it : fragments) {
31  Beatmup::DebugAssertion::check(it.length > 0,
32  "Non-positive length in fragment %d: %d", index, it.length);
33  Beatmup::DebugAssertion::check(it.offset >= 0,
34  "Negative offset in fragment %d: %d", index, it.offset);
35  Beatmup::DebugAssertion::check(it.length + it.offset <= it.fragment->getSampleCount(),
36  "Out-of-range mapping of fragment %d: %d", index, it.fragment->getSampleCount());
37  index++;
38  }
39 }
40 #endif
41 
43  cumTimes.push_back(0);
44 }
45 
46 
48  if (pointers.size() > 0)
49  BEATMUP_DEBUG_E("Deleting pointed sequence");
50 }
51 
52 
54  int index = fragments.size() / 2, step = std::max(index / 2, 1);
55 
56  // simple log search
57  do {
58  if (cumTimes[index] > time)
59  index -= step;
60  else
61  if (time >= cumTimes[index+1])
62  index += step;
63  else
64  // target fragment reached
65  return index;
66 
67  // halfing the step
68  step = std::max(step / 2, 1);
69  } while (0 <= index && index < fragments.size());
70 
71  // no fragment found, the index is outside of the sequence scope
72  if (index < 0)
73  return VOID_LEFT;
74  return VOID_RIGHT;
75 }
76 
77 
78 void Sequence::splitFragment(int index, dtime dt) {
79  if (dt > 0) {
80  fragments.insert(fragments.begin() + index, fragments[index]);
81  fragments[index].length = dt;
82  fragments[index + 1].length -= dt;
83  fragments[index + 1].offset += dt;
84  }
85 }
86 
87 
88 void Sequence::concatenate(Fragment& fragment, dtime offset, dtime length) {
89  fragments.emplace_back(fragment, offset, length);
90  cumTimes.push_back(cumTimes.back() + length);
91 }
92 
93 
95  for (auto ptr : pointers)
96  ptr->moveTo(ptr->getTime());
97 }
98 
99 
100 void Sequence::clear() {
101  fragments.clear();
102  cumTimes.clear();
103  cumTimes.push_back(0);
104  syncPointers();
105 }
106 
107 
108 void Sequence::shrink(dtime timeLeft, dtime timeRight) {
109  if (timeLeft >= timeRight || timeLeft >= getDuration() || timeRight <= 0) {
110  clear();
111  return;
112  }
113  int
114  leftFragment = findFragment(timeLeft),
115  rightFragment = findFragment(timeRight);
116 
117  // FIXME: time bounds can fall out of the sequence scope
118  BEATMUP_ASSERT_DEBUG(leftFragment >= 0 && rightFragment >= 0);
119 
120  // right bound may generate an empty fragment
121  if (cumTimes[rightFragment] == timeRight)
122  --rightFragment;
123 
124  // shrinking fragment list if needed
125  if (rightFragment != fragments.size() - 1)
126  fragments.erase(fragments.begin() + rightFragment + 1, fragments.end());
127  if (leftFragment > 0)
128  fragments.erase(fragments.begin(), fragments.begin() + leftFragment);
129 
130  // adjust time bounds
131  dtime dt = timeLeft - cumTimes[leftFragment];
132  fragments[0].offset += dt;
133  fragments[0].length -= dt;
134  fragments.back().length = timeRight - cumTimes[rightFragment];
135 
136  // recompute cumulative time index
137  cumTimes.clear();
138  cumTimes.reserve(fragments.size() + 1);
139  cumTimes.push_back(0);
140 
141  // special case: single fragment left after shrinking
142  if (leftFragment == rightFragment) {
143  dt = timeRight - timeLeft;
144  cumTimes.push_back(dt);
145  fragments.back().length = dt;
146  }
147  else {
148  dtime time = 0;
149  for (const auto &it : fragments)
150  cumTimes.push_back(time += it.length);
151  }
152 
153  // synchronize pointers
154  syncPointers();
155 
156 #ifdef BEATMUP_DEBUG
157  consistencyTest(fragments);
158 #endif
159 }
160 
161 
162 Sequence* Sequence::copy(dtime fromTime, dtime toTime) const {
163  if (fromTime >= toTime)
164  return nullptr; // nothing to copy
165 
166  int
167  fromFragment = findFragment(fromTime),
168  toFragment = findFragment(toTime);
169 
170  // whole sequence is apart
171  if ((toFragment == VOID_LEFT || toFragment == VOID_RIGHT) && fromFragment == toFragment)
172  return nullptr;
173 
174  // now we are sure that there is some data to copy
175  if (fromFragment == VOID_LEFT || toFragment == VOID_RIGHT)
176  throw Sequence::AccessException("A border is out of sequence scope", *this);
177 
178  // right bound may generate an empty fragment
179  if (cumTimes[toFragment] == toTime)
180  --toFragment;
181 
183  int numFragments = toFragment - fromFragment + 1;
184  result->fragments.reserve(numFragments);
185  result->cumTimes.clear();
186  result->cumTimes.reserve(numFragments + 1);
187  result->cumTimes.push_back(0);
188 
189  // put first fragment
190  dtime dt = fromTime - cumTimes[fromFragment];
191  result->fragments.emplace_back(fragments[fromFragment]);
192  result->fragments[0].offset += dt;
193 
194  // special case: single fragment copied
195  if (fromFragment == toFragment) {
196  dt = toTime - fromTime;
197  result->fragments[0].length = dt;
198  result->cumTimes.push_back(dt);
199  }
200  else {
201  dtime cumTime = (result->fragments[0].length -= dt);
202  result->cumTimes.push_back(cumTime);
203 
204  // go
205  for (int i = fromFragment + 1; i <= toFragment - 1; ++i) {
206  // putting new fragment
207  result->fragments.emplace_back(fragments[i]);
208  cumTime += fragments[i].length;
209  result->cumTimes.push_back(cumTime);
210  }
211 
212  // right boundary
213  result->fragments.emplace_back(fragments[toFragment]);
214  dtime l = toTime - cumTimes[toFragment];
215  result->fragments.back().length = l;
216  result->cumTimes.push_back(cumTime + l);
217  }
218 
219 #ifdef BEATMUP_DEBUG
220  consistencyTest(fragments);
221 #endif
222  return result;
223 }
224 
225 
226 void Sequence::insert(const Sequence& sequence, dtime time) {
227  // check if the time is okay
228  if (time < 0 || time > getDuration()) // time may be equal to the length
229  throw AccessException("Bad insert position", *this);
230 
231  // find the fragment (guaranteeing that it is split it in two)
232  int index = findFragment(time);
233  if (time > cumTimes[index]) {
234  dtime dt = time - cumTimes[index];
235  splitFragment(index, dt);
236  index++;
237  cumTimes[index] = cumTimes[index - 1] + dt;
238  }
239 
240  // insert remaining part of the sequence
241  fragments.insert(fragments.begin() + index, sequence.fragments.begin(), sequence.fragments.end());
242  cumTimes.resize(fragments.size() + 1);
243  dtime cumTime = cumTimes[index];
244  for (auto it = fragments.begin() + index; it != fragments.end(); ++it)
245  cumTimes[++index] = (cumTime += it->length);
246 
247  syncPointers();
248 #ifdef BEATMUP_DEBUG
249  consistencyTest(fragments);
250 #endif
251 }
252 
253 
254 void Sequence::remove(dtime fromTime, dtime toTime) {
255  if (fromTime >= toTime)
256  throw AccessException("Inconsistent time bounds when removing", *this);
257 
258  // nothing to remove
259  if (toTime < 0 || getDuration() <= fromTime)
260  return;
261 
262  // whole sequence erased
263  if (fromTime <= 0 || toTime > getDuration()) {
264  clear();
265  return;
266  }
267 
268  int
269  fromFragment = fromTime >= cumTimes[1] ? findFragment(fromTime) : 0,
270  toFragment = findFragment(toTime);
271 
272  // determine fragment indices range to remove
273  dtime dt = fromTime - cumTimes[fromFragment];
274  int removeRangeBegin = fromFragment;
275  if (dt > 0)
276  removeRangeBegin++;
277  int removeRangeEnd = toFragment >= 0 ? toFragment : fragments.size();
278 
279  // remove fragments
280  if (removeRangeBegin <= removeRangeEnd) {
281  if (removeRangeBegin < removeRangeEnd)
282  fragments.erase(fragments.begin() + removeRangeBegin, toFragment >= 0 ? fragments.begin() + toFragment : fragments.end());
283 
284  // adjust boundary fragments timings
285  int index = fromFragment;
286  if (dt > 0) {
287  fragments[index].length = dt;
288  index++;
289  }
290  if (toFragment >= 0) {
291  dt = toTime - cumTimes[toFragment];
292  fragments[index].offset += dt;
293  fragments[index].length -= dt;
294  }
295  }
296  // special case: single fragment concerned
297  else {
298  dtime dt = fromTime - cumTimes[fromFragment];
299  splitFragment(fromFragment, dt);
300  dt = toTime - fromTime;
301  int index = fromFragment + 1;
302  fragments[index].offset += dt;
303  fragments[index].length -= dt;
304  }
305 
306  // update cumulative time index
307  cumTimes.resize(fragments.size() + 1);
308  int index = fromFragment;
309  dtime cumTime = cumTimes[index];
310  for (auto it = fragments.begin() + index; it != fragments.end(); ++it)
311  cumTimes[++index] = (cumTime += it->length);
312 
313  syncPointers();
314 
315 #ifdef BEATMUP_DEBUG
316  consistencyTest(fragments);
317 #endif
318 }
319 
320 
321 void Sequence::split(int time, Sequence* left, Sequence* right) const {
322  const dtime duration = getDuration();
323  if (time <= 0) {
324  left = nullptr;
325  right = copy(0, duration);
326  }
327  else if (time >= duration - 1) {
328  left = copy(0, duration);
329  right = nullptr;
330  }
331  else {
332  left = copy(0, time);
333  right = copy(time, duration);
334  }
335 }
336 
337 
338 
339 Sequence::Pointer::Pointer(Sequence& sequence, dtime time, bool writing) : writing(writing), watching(false), sequence(sequence) {
340  if (0 <= time && time < sequence.cumTimes[1]) {
341  // an heuristic to start quickly
342  if (writing)
343  sequence.fragments[0].editData();
344  currentTime = time;
346  pointer.offset += time;
347  pointer.length -= time;
348  fragmentIdx = 0;
349  }
350  else
351  moveTo(time);
352 }
353 
354 
356  if (watching)
357  sequence.pointers.erase(std::find(sequence.pointers.begin(), sequence.pointers.end(), this));
358 }
359 
360 
362  currentTime = time;
363  fragmentIdx = sequence.findFragment(time);
364  if (fragmentIdx != VOID_LEFT && fragmentIdx != VOID_RIGHT) {
365  if (writing)
366  sequence.fragments[fragmentIdx].editData();
367  pointer = sequence.fragments[fragmentIdx];
368  dtime dt = time - sequence.cumTimes[fragmentIdx];
369  pointer.offset += dt;
370  pointer.length -= dt;
371  }
372  else
373  pointer.nullify();
374 }
375 
376 
378  if (0 <= fragmentIdx && fragmentIdx < sequence.fragments.size()-1) {
379  fragmentIdx++;
380  pointer = sequence.fragments[fragmentIdx];
381  currentTime = sequence.cumTimes[fragmentIdx];
382  }
383  else {
384  fragmentIdx = VOID_RIGHT;
385  pointer.nullify();
386  currentTime = sequence.getDuration();
387  }
388 }
389 
390 
392  if (!watching) {
393  watching = true;
394  sequence.pointers.push_back(this);
395  }
396 }
Represents a continuous set of data samples.
Definition: fragment.h:31
void moveTo(dtime time)
Sets pointer to a specific time.
Definition: sequence.cpp:361
FragmentPtr pointer
pointed fragment
Definition: sequence.h:47
void watch()
Enables "watching mode", i.e., if the sequence is modified, the pointer will follow the modifications...
Definition: sequence.cpp:391
Pointer(Sequence &sequence, dtime time, bool writing)
Definition: sequence.cpp:339
void step()
Moves the pointer forward an arbitrary number of samples.
Definition: sequence.cpp:377
bool writing
if true, the pointer is used to modify the data
Definition: sequence.h:40
Fragmented signal in time domain.
Definition: sequence.h:33
std::vector< Pointer * > pointers
pointers currently accessing the sequence
Definition: sequence.h:81
int findFragment(dtime time) const
Log search for a fragment containing given time moment.
void remove(dtime fromTime, dtime toTime)
Erases a part of the sequence between two given time moments.
void shrink(dtime timeLeft, dtime timeRight)
Shrinks the sequence to given time bounds.
const dtime getDuration() const
Returns sequence duration in number of samples.
Definition: sequence.h:123
static const int VOID_RIGHT
returned by getFragment() when the entire sequence is on the left of the given time moment
Definition: sequence.h:98
void splitFragment(int index, dtime delta)
Splits a given fragment in two.
void split(dtime time, Sequence *left, Sequence *right) const
Splits the sequence in two at a specific time.
std::vector< dtime > cumTimes
cumulative sum of fragment lengths, starts from 0, of N+1 entries (N = num. of fragments)
Definition: sequence.h:80
virtual Sequence * createEmpty() const =0
Initializes an empty sequence, used to bootstrap copying operations.
void syncPointers()
Resets pointers once the sequence changes to keep them consistent.
void concatenate(Fragment &fragment, dtime offset, dtime length)
Adds a new fragment at the end of the sequence.
void clear()
Removes the content of the sequence making it empty (of zero duration).
std::vector< FragmentPtr > fragments
the content
Definition: sequence.h:79
static const int VOID_LEFT
returned by getFragment() when the entire sequence is on the right of the given time moment
Definition: sequence.h:97
void insert(const Sequence &sequence, dtime time)
Inserts a Sequence at a given position in time.
Sequence * copy(dtime fromTime, dtime toTime) const
Copies a given piece of the current sequence into new Sequence.
#define BEATMUP_DEBUG_E(...)
Definition: debug.h:34
#define BEATMUP_ASSERT_DEBUG(C)
Definition: exception.h:163
Abstract fragmented signals representation.
Definition: fragment.h:27
int dtime
discrete time
Definition: basic_types.h:37
CustomPoint< numeric > max(const CustomPoint< numeric > &a, const CustomPoint< numeric > &b)
Definition: geometry.h:728
int offset
offset in samples inside the fragment since its beginning
Definition: fragment.h:70
int length
number of samples to use from the fragment
Definition: fragment.h:71
JNIEnv jobject jint jint jint jfloat fragment
jlong jint index
Beatmup::IntPoint result
jlong jlong jint time
jlong jlong jint jint jint jint jint left
jlong jint jfloat step