1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application.
5 *
6 * ReZound is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * ReZound is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20
21 #include "CShortenQuietAreasAction.h"
22
23 #include "../CActionParameters.h"
24 #include "../unit_conv.h"
25 #include "../DSP/LevelDetector.h"
26
27 #include <istring>
28
29 #include <utility>
30 #include <algorithm>
31
32
CShortenQuietAreasAction(const AActionFactory * factory,const CActionSound * actionSound,const float _quietThreshold,const float _quietTime,const float _unquietTime,const float _detectorWindow,const float _shortenFactor)33 CShortenQuietAreasAction::CShortenQuietAreasAction(const AActionFactory *factory,const CActionSound *actionSound,const float _quietThreshold,const float _quietTime,const float _unquietTime,const float _detectorWindow,const float _shortenFactor) :
34 AAction(factory,actionSound),
35 quietThreshold(_quietThreshold),
36 quietTime(_quietTime),
37 unquietTime(_unquietTime),
38 detectorWindow(_detectorWindow),
39 shortenFactor(_shortenFactor),
40
41 undoRemoveLength(0)
42 {
43 if(shortenFactor<0.0 || shortenFactor>1.0)
44 throw runtime_error(string(__func__)+" shortenFactor is out of range: "+istring(shortenFactor));
45 }
46
~CShortenQuietAreasAction()47 CShortenQuietAreasAction::~CShortenQuietAreasAction()
48 {
49 }
50
doActionSizeSafe(CActionSound * actionSound,bool prepareForUndo)51 bool CShortenQuietAreasAction::doActionSizeSafe(CActionSound *actionSound,bool prepareForUndo)
52 {
53 if(shortenFactor==1.0)
54 return true;
55
56 const sample_pos_t start=actionSound->start;
57 const sample_pos_t stop=actionSound->stop;
58 const sample_pos_t selectionLength=actionSound->selectionLength();
59
60 undoRemoveLength=selectionLength; // we shrink this each time space is removed
61
62 moveSelectionToTempPools(actionSound,mmSelection,actionSound->selectionLength(),(sample_pos_t)quietTime); // fudge by quietTime.. we might read past the end for crossfading
63
64 const sample_t quietThreshold=dBFS_to_amp(this->quietThreshold);
65 const sample_pos_t quietTime=max((sample_pos_t)1,ms_to_samples(this->quietTime,actionSound->sound->getSampleRate()));
66 const sample_pos_t unquietTime=ms_to_samples(this->unquietTime,actionSound->sound->getSampleRate());
67
68
69 /*
70 * The algorithm follows this state machine:
71 *
72 * >quiet threshold <=quiet threshold
73 * ------------------------------ ---------------------------
74 * | | | |
75 * | | | |
76 * V | V |
77 * --- <=quiet threshold --- >=quiet time --- >quiet treshold ---
78 * -------> | 0 | -----------------------> | 1 | ------------------> | 2 | --------------------> | 3 |
79 * --- --- [position noted --- ---
80 * ^ within audio] |
81 * | |
82 * | |
83 * | shorten area between noted position and here |
84 * ------------------------------------------------------------------------------------
85 * >=unquiet time
86 */
87
88 int state=0;
89 sample_pos_t dQuietBeginPos=start;
90 sample_pos_t dQuietEndPos;
91 sample_pos_t sQuietBeginPos=0;
92 sample_pos_t sQuietEndPos;
93 sample_pos_t quietCounter;
94 sample_pos_t unquietCounter;
95 CDSPRMSLevelDetector levelDetector(max((sample_pos_t)1,ms_to_samples(detectorWindow,actionSound->sound->getSampleRate())));
96
97 auto_array<const CRezPoolAccesser> srces(MAX_CHANNELS);
98 auto_array<const CRezPoolAccesser> alt_srces(MAX_CHANNELS); // used in order to be more efficient.. the crossfade code reads from two positions in the src data
99
100 sample_pos_t destPos=start;
101 auto_array<CRezPoolAccesser> dests(MAX_CHANNELS);
102
103 // create accessors to write to
104 const unsigned channelCount=actionSound->sound->getChannelCount();
105 for(unsigned i=0;i<channelCount;i++)
106 {
107 srces[i]=new CRezPoolAccesser(actionSound->sound->getTempAudio(tempAudioPoolKey,i));
108 alt_srces[i]=new CRezPoolAccesser(actionSound->sound->getTempAudio(tempAudioPoolKey,i));
109 dests[i]=new CRezPoolAccesser(actionSound->sound->getAudio(i));
110 }
111
112 CStatusBar statusBar(_("Shortening Quiet Areas"),0,selectionLength,true);
113 sample_pos_t srcPos;
114 for(srcPos=0;srcPos<selectionLength;srcPos++,destPos++)
115 {
116 if(statusBar.update(srcPos))
117 { // cancelled
118 if(prepareForUndo)
119 undoActionSizeSafe(actionSound);
120 else
121 {
122 for(unsigned i=0;i<channelCount;i++)
123 actionSound->sound->invalidatePeakData(i,actionSound->start,destPos);
124 }
125
126 return false;
127 }
128
129 const sample_t s=(*(srces[0]))[srcPos]; // ??? which channel to use or both? (prompt the user!)
130 const mix_sample_t l=levelDetector.readLevel(s);
131
132 switch(state)
133 {
134 case 0: // waiting for level to fall below threshold
135 if(l<=quietThreshold)
136 {
137 state=1;
138 quietCounter=0;
139 dQuietBeginPos=destPos;
140 sQuietBeginPos=srcPos;
141 }
142
143 break;
144
145 case 1: // waiting for level that is below threshold to remain that way for quiet time
146 if(l>quietThreshold)
147 { // level wasn't below threshold for long enough
148 state=0;
149 }
150 else if(quietCounter>=quietTime)
151 { // level has now been below the threshold for long enough
152 state=2;
153 // position of quiet areas start is noted because dQuietBeginPos has been updated in state 0
154 }
155 else
156 quietCounter++;
157
158 break;
159
160 case 2: // level has been below the threshold for quiet time samples, now waiting for it to go back above the threshold
161 if(l>quietThreshold)
162 {
163 state=3;
164 unquietCounter=0;
165 dQuietEndPos=destPos;
166 sQuietEndPos=srcPos;
167 }
168
169 break;
170
171 case 3: // waiting for level to remain above the quiet threshold for unquiet time samples
172 if(l<=quietThreshold)
173 { // level has fallen back below the treshold, start over waiting for unquiet time
174 state=2;
175 }
176 else if(unquietCounter>=unquietTime)
177 { // level has now been above the threshold for unquiet time samples
178 state=0;
179
180 alterQuietAreaLength(actionSound,destPos,unquietCounter,dQuietBeginPos,dQuietEndPos,sQuietBeginPos,sQuietEndPos,srces,alt_srces,dests);
181 }
182 else
183 unquietCounter++;
184
185 break;
186 }
187
188 // copying the data as we go
189 (*(dests[0]))[destPos]=s;
190 for(unsigned i=1;i<channelCount;i++)
191 (*(dests[i]))[destPos]=(*(srces[i]))[srcPos];
192 }
193
194 if(state==2)
195 { // were left within quiet area but it never remained unquiet long enough to take the arc back to state 0, but we still should shorten the quiet area
196
197 // this is what happens wend going from state 2 -> 3 .. except we subtract 1 because we don't want to overstep the boundary
198 unquietCounter=0;
199 dQuietEndPos=destPos-1;
200 sQuietEndPos=srcPos-1;
201
202 alterQuietAreaLength(actionSound,destPos,unquietCounter,dQuietBeginPos,dQuietEndPos,sQuietBeginPos,sQuietEndPos,srces,alt_srces,dests);
203 }
204
205 if(!prepareForUndo)
206 freeAllTempPools(); // free the temp pools if we don't need to keep the backup copy around
207
208 // new selectStop position
209 actionSound->stop=destPos-1;
210
211 return true;
212 }
213
alterQuietAreaLength(CActionSound * actionSound,sample_pos_t & destPos,const sample_pos_t unquietCounter,const sample_pos_t dQuietBeginPos,const sample_pos_t dQuietEndPos,const sample_pos_t sQuietBeginPos,const sample_pos_t sQuietEndPos,auto_array<const CRezPoolAccesser> & srces,auto_array<const CRezPoolAccesser> & alt_srces,auto_array<CRezPoolAccesser> & dests)214 void CShortenQuietAreasAction::alterQuietAreaLength(CActionSound *actionSound,sample_pos_t &destPos,const sample_pos_t unquietCounter,const sample_pos_t dQuietBeginPos,const sample_pos_t dQuietEndPos,const sample_pos_t sQuietBeginPos,const sample_pos_t sQuietEndPos,auto_array<const CRezPoolAccesser> &srces,auto_array<const CRezPoolAccesser> &alt_srces,auto_array<CRezPoolAccesser> &dests)
215 {
216 // it's time to alter length of quiet area
217 // - remove an appropriately sized section between the dQuietBeginPos and dQuietEndPos
218 // - create a crossfade using data from srces
219
220 // the crossfade looks something like:
221 //
222 // before delete region | within delete region | after delete region
223 // \ | /|
224 // \ | / |
225 // \ | / |
226 // \| / |
227 //
228 // |_______| |_______|
229 // fade 1 region fade 2 region
230 // fading out fading in
231 //
232 // when the region to be deleted is deleted, then the two points in time at the two vertical positions become the same point in time
233 // and the crossfade is the result of adding the signals from fade 1 and fade 2 together
234 // fade 1's and fade 2's covered region may overlap if the region to be deleted is smaller than half the fade
235 //
236 // the implementation does the delete first, then goes back to the src to calculate the data surounding the point of deletion
237
238 const unsigned channelCount=actionSound->sound->getChannelCount();
239
240 const sample_pos_t dQuietAreaLength=(dQuietEndPos-dQuietBeginPos)+1;
241
242 if(dQuietAreaLength>1)
243 { // don't do anything that at least doesn't affect two samples
244
245 const sample_pos_t dDeleteLength=(sample_pos_t)sample_fpos_round((sample_fpos_t)dQuietAreaLength*(1.0-shortenFactor));
246 const sample_pos_t dDeletePos=dQuietBeginPos+((dQuietAreaLength-dDeleteLength)/2); // split the different
247 sample_pos_t sDeletePos=sQuietBeginPos+((dQuietAreaLength-dDeleteLength)/2); // same as dDeletePos except mapped into src space instead of dest space
248
249 actionSound->sound->removeSpace(dDeletePos,dDeleteLength);
250 undoRemoveLength-=dDeleteLength;
251 destPos-=dDeleteLength;
252
253 // the crossfade time is based on the "Must Remain Quiet For" parameter (but not more than we have remained unquiet, otherwise we'll write into a previous crossfade) and a minimum of 1ms (but an even smaller minimum if we don't have enough data in srces to do 1ms of crossfade)
254 const sample_pos_t crossfadeTime=min(sDeletePos, max((sample_pos_t)(actionSound->sound->getSampleRate()/1000), min(unquietCounter,(sample_pos_t)quietTime) ));
255 if(crossfadeTime>0)
256 {
257
258 sample_pos_t fade1Pos=sDeletePos-crossfadeTime;
259 sample_pos_t fade2Pos=(sDeletePos+dDeleteLength)-crossfadeTime;
260 sample_pos_t writePos=dDeletePos-crossfadeTime;
261
262 for(sample_pos_t t=0;t<crossfadeTime;t++)
263 {
264 const float g=(float)t/(float)crossfadeTime;
265
266 for(unsigned i=0;i<channelCount;i++)
267 (*(dests[i]))[writePos]=ClipSample( (1.0-g)*( (*(srces[i]))[fade1Pos]) + g*( (*(alt_srces[i]))[fade2Pos]) );
268
269 fade1Pos++;
270 fade2Pos++;
271 writePos++;
272 }
273 }
274 }
275 }
276
canUndo(const CActionSound * actionSound) const277 AAction::CanUndoResults CShortenQuietAreasAction::canUndo(const CActionSound *actionSound) const
278 {
279 return curYes;
280 }
281
undoActionSizeSafe(const CActionSound * actionSound)282 void CShortenQuietAreasAction::undoActionSizeSafe(const CActionSound *actionSound)
283 {
284 if(shortenFactor==1.0)
285 return;
286
287 restoreSelectionFromTempPools(actionSound,actionSound->start,undoRemoveLength);
288 }
289
290
291 // --------------------------------------------------
292
CShortenQuietAreasActionFactory(AActionDialog * normalDialog)293 CShortenQuietAreasActionFactory::CShortenQuietAreasActionFactory(AActionDialog *normalDialog) :
294 AActionFactory(N_("Shorten Quiet Areas"),_("Shorten Quiet Areas with Cues"),NULL,normalDialog)
295 {
296 }
297
~CShortenQuietAreasActionFactory()298 CShortenQuietAreasActionFactory::~CShortenQuietAreasActionFactory()
299 {
300 }
301
manufactureAction(const CActionSound * actionSound,const CActionParameters * actionParameters) const302 CShortenQuietAreasAction *CShortenQuietAreasActionFactory::manufactureAction(const CActionSound *actionSound,const CActionParameters *actionParameters) const
303 {
304 return new CShortenQuietAreasAction(
305 this,
306 actionSound,
307 actionParameters->getValue<float>("Threshold for Quiet"),
308 actionParameters->getValue<float>("Must Remain Quiet for"),
309 actionParameters->getValue<float>("Must Remain Unquiet for"),
310 actionParameters->getValue<float>("Level Detector Window Time"),
311 actionParameters->getValue<float>("Shorten Found Area To")
312 );
313 }
314
315
316