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