1 /*
2  * InstrumentSoundShaping.cpp - implementation of class InstrumentSoundShaping
3  *
4  * Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
5  *
6  * This file is part of LMMS - https://lmms.io
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public
19  * License along with this program (see COPYING); if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301 USA.
22  *
23  */
24 
25 #include <QDomElement>
26 
27 #include "InstrumentSoundShaping.h"
28 #include "BasicFilters.h"
29 #include "embed.h"
30 #include "Engine.h"
31 #include "EnvelopeAndLfoParameters.h"
32 #include "Instrument.h"
33 #include "InstrumentTrack.h"
34 #include "Mixer.h"
35 
36 
37 const float CUT_FREQ_MULTIPLIER = 6000.0f;
38 const float RES_MULTIPLIER = 2.0f;
39 const float RES_PRECISION = 1000.0f;
40 
41 
42 // names for env- and lfo-targets - first is name being displayed to user
43 // and second one is used internally, e.g. for saving/restoring settings
44 const QString InstrumentSoundShaping::targetNames[InstrumentSoundShaping::NumTargets][3] =
45 {
46 	{ InstrumentSoundShaping::tr( "VOLUME" ), "vol",
47 			InstrumentSoundShaping::tr( "Volume" ) },
48 /*	InstrumentSoundShaping::tr( "Pan" ),
49 	InstrumentSoundShaping::tr( "Pitch" ),*/
50 	{ InstrumentSoundShaping::tr( "CUTOFF" ), "cut",
51 			InstrumentSoundShaping::tr( "Cutoff frequency" ) },
52 	{ InstrumentSoundShaping::tr( "RESO" ), "res",
53 			InstrumentSoundShaping::tr( "Resonance" ) }
54 } ;
55 
56 
57 
InstrumentSoundShaping(InstrumentTrack * _instrument_track)58 InstrumentSoundShaping::InstrumentSoundShaping(
59 					InstrumentTrack * _instrument_track ) :
60 	Model( _instrument_track, tr( "Envelopes/LFOs" ) ),
61 	m_instrumentTrack( _instrument_track ),
62 	m_filterEnabledModel( false, this ),
63 	m_filterModel( this, tr( "Filter type" ) ),
64 	m_filterCutModel( 14000.0, 1.0, 14000.0, 1.0, this, tr( "Cutoff frequency" ) ),
65 	m_filterResModel( 0.5, BasicFilters<>::minQ(), 10.0, 0.01, this, tr( "Q/Resonance" ) )
66 {
67 	for( int i = 0; i < NumTargets; ++i )
68 	{
69 		float value_for_zero_amount = 0.0;
70 		if( i == Volume )
71 		{
72 			value_for_zero_amount = 1.0;
73 		}
74 		m_envLfoParameters[i] = new EnvelopeAndLfoParameters(
75 										value_for_zero_amount,
76 										this );
77 		m_envLfoParameters[i]->setDisplayName(
78 			tr( targetNames[i][2].toUtf8().constData() ) );
79 	}
80 
81 	m_filterModel.addItem( tr( "LowPass" ), new PixmapLoader( "filter_lp" ) );
82 	m_filterModel.addItem( tr( "HiPass" ), new PixmapLoader( "filter_hp" ) );
83 	m_filterModel.addItem( tr( "BandPass csg" ), new PixmapLoader( "filter_bp" ) );
84 	m_filterModel.addItem( tr( "BandPass czpg" ), new PixmapLoader( "filter_bp" ) );
85 	m_filterModel.addItem( tr( "Notch" ), new PixmapLoader( "filter_notch" ) );
86 	m_filterModel.addItem( tr( "Allpass" ), new PixmapLoader( "filter_ap" ) );
87 	m_filterModel.addItem( tr( "Moog" ), new PixmapLoader( "filter_lp" ) );
88 	m_filterModel.addItem( tr( "2x LowPass" ), new PixmapLoader( "filter_2lp" ) );
89 	m_filterModel.addItem( tr( "RC LowPass 12dB" ), new PixmapLoader( "filter_lp" ) );
90 	m_filterModel.addItem( tr( "RC BandPass 12dB" ), new PixmapLoader( "filter_bp" ) );
91 	m_filterModel.addItem( tr( "RC HighPass 12dB" ), new PixmapLoader( "filter_hp" ) );
92 	m_filterModel.addItem( tr( "RC LowPass 24dB" ), new PixmapLoader( "filter_lp" ) );
93 	m_filterModel.addItem( tr( "RC BandPass 24dB" ), new PixmapLoader( "filter_bp" ) );
94 	m_filterModel.addItem( tr( "RC HighPass 24dB" ), new PixmapLoader( "filter_hp" ) );
95 	m_filterModel.addItem( tr( "Vocal Formant Filter" ), new PixmapLoader( "filter_hp" ) );
96 	m_filterModel.addItem( tr( "2x Moog" ), new PixmapLoader( "filter_2lp" ) );
97 	m_filterModel.addItem( tr( "SV LowPass" ), new PixmapLoader( "filter_lp" ) );
98 	m_filterModel.addItem( tr( "SV BandPass" ), new PixmapLoader( "filter_bp" ) );
99 	m_filterModel.addItem( tr( "SV HighPass" ), new PixmapLoader( "filter_hp" ) );
100 	m_filterModel.addItem( tr( "SV Notch" ), new PixmapLoader( "filter_notch" ) );
101 	m_filterModel.addItem( tr( "Fast Formant" ), new PixmapLoader( "filter_hp" ) );
102 	m_filterModel.addItem( tr( "Tripole" ), new PixmapLoader( "filter_lp" ) );
103 }
104 
105 
106 
107 
~InstrumentSoundShaping()108 InstrumentSoundShaping::~InstrumentSoundShaping()
109 {
110 }
111 
112 
113 
114 
volumeLevel(NotePlayHandle * n,const f_cnt_t frame)115 float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t frame )
116 {
117 	f_cnt_t envReleaseBegin = frame - n->releaseFramesDone() + n->framesBeforeRelease();
118 
119 	if( n->isReleased() == false )
120 	{
121 		envReleaseBegin += Engine::mixer()->framesPerPeriod();
122 	}
123 
124 	float level;
125 	m_envLfoParameters[Volume]->fillLevel( &level, frame, envReleaseBegin, 1 );
126 
127 	return level;
128 }
129 
130 
131 
132 
processAudioBuffer(sampleFrame * buffer,const fpp_t frames,NotePlayHandle * n)133 void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
134 							const fpp_t frames,
135 							NotePlayHandle* n )
136 {
137 	const f_cnt_t envTotalFrames = n->totalFramesPlayed();
138 	f_cnt_t envReleaseBegin = envTotalFrames - n->releaseFramesDone() + n->framesBeforeRelease();
139 
140 	if( !n->isReleased() || ( n->instrumentTrack()->isSustainPedalPressed() &&
141 		!n->isReleaseStarted() ) )
142 	{
143 		envReleaseBegin += frames;
144 	}
145 
146 	// because of optimizations, there's special code for several cases:
147 	// 	- cut- and res-lfo/envelope active
148 	// 	- cut-lfo/envelope active
149 	// 	- res-lfo/envelope active
150 	//	- no lfo/envelope active but filter is used
151 
152 	// only use filter, if it is really needed
153 
154 	if( m_filterEnabledModel.value() )
155 	{
156 		float cutBuffer [frames];
157 		float resBuffer [frames];
158 
159 		int old_filter_cut = 0;
160 		int old_filter_res = 0;
161 
162 		if( n->m_filter == NULL )
163 		{
164 			n->m_filter = new BasicFilters<>( Engine::mixer()->processingSampleRate() );
165 		}
166 		n->m_filter->setFilterType( m_filterModel.value() );
167 
168 		if( m_envLfoParameters[Cut]->isUsed() )
169 		{
170 			m_envLfoParameters[Cut]->fillLevel( cutBuffer, envTotalFrames, envReleaseBegin, frames );
171 		}
172 		if( m_envLfoParameters[Resonance]->isUsed() )
173 		{
174 			m_envLfoParameters[Resonance]->fillLevel( resBuffer, envTotalFrames, envReleaseBegin, frames );
175 		}
176 
177 		const float fcv = m_filterCutModel.value();
178 		const float frv = m_filterResModel.value();
179 
180 		if( m_envLfoParameters[Cut]->isUsed() &&
181 			m_envLfoParameters[Resonance]->isUsed() )
182 		{
183 			for( fpp_t frame = 0; frame < frames; ++frame )
184 			{
185 				const float new_cut_val = EnvelopeAndLfoParameters::expKnobVal( cutBuffer[frame] ) *
186 								CUT_FREQ_MULTIPLIER + fcv;
187 
188 				const float new_res_val = frv + RES_MULTIPLIER * resBuffer[frame];
189 
190 				if( static_cast<int>( new_cut_val ) != old_filter_cut ||
191 					static_cast<int>( new_res_val*RES_PRECISION ) != old_filter_res )
192 				{
193 					n->m_filter->calcFilterCoeffs( new_cut_val, new_res_val );
194 					old_filter_cut = static_cast<int>( new_cut_val );
195 					old_filter_res = static_cast<int>( new_res_val*RES_PRECISION );
196 				}
197 
198 				buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 );
199 				buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
200 			}
201 		}
202 		else if( m_envLfoParameters[Cut]->isUsed() )
203 		{
204 			for( fpp_t frame = 0; frame < frames; ++frame )
205 			{
206 				float new_cut_val = EnvelopeAndLfoParameters::expKnobVal( cutBuffer[frame] ) *
207 								CUT_FREQ_MULTIPLIER + fcv;
208 
209 				if( static_cast<int>( new_cut_val ) != old_filter_cut )
210 				{
211 					n->m_filter->calcFilterCoeffs( new_cut_val, frv );
212 					old_filter_cut = static_cast<int>( new_cut_val );
213 				}
214 
215 				buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 );
216 				buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
217 			}
218 		}
219 		else if( m_envLfoParameters[Resonance]->isUsed() )
220 		{
221 			for( fpp_t frame = 0; frame < frames; ++frame )
222 			{
223 				float new_res_val = frv + RES_MULTIPLIER * resBuffer[frame];
224 
225 				if( static_cast<int>( new_res_val*RES_PRECISION ) != old_filter_res )
226 				{
227 					n->m_filter->calcFilterCoeffs( fcv, new_res_val );
228 					old_filter_res = static_cast<int>( new_res_val*RES_PRECISION );
229 				}
230 
231 				buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 );
232 				buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
233 			}
234 		}
235 		else
236 		{
237 			n->m_filter->calcFilterCoeffs( fcv, frv );
238 
239 			for( fpp_t frame = 0; frame < frames; ++frame )
240 			{
241 				buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 );
242 				buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
243 			}
244 		}
245 	}
246 
247 	if( m_envLfoParameters[Volume]->isUsed() )
248 	{
249 		float volBuffer [frames];
250 		m_envLfoParameters[Volume]->fillLevel( volBuffer, envTotalFrames, envReleaseBegin, frames );
251 
252 		for( fpp_t frame = 0; frame < frames; ++frame )
253 		{
254 			float vol_level = volBuffer[frame];
255 			vol_level = vol_level * vol_level;
256 			buffer[frame][0] = vol_level * buffer[frame][0];
257 			buffer[frame][1] = vol_level * buffer[frame][1];
258 		}
259 	}
260 
261 /*	else if( m_envLfoParameters[Volume]->isUsed() == false && m_envLfoParameters[PANNING]->isUsed() )
262 	{
263 		// only use panning-envelope...
264 		for( fpp_t frame = 0; frame < frames; ++frame )
265 		{
266 			float vol_level = pan_buf[frame];
267 			vol_level = vol_level*vol_level;
268 			for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl )
269 			{
270 				buffer[frame][chnl] = vol_level * buffer[frame][chnl];
271 			}
272 		}
273 	}*/
274 }
275 
276 
277 
278 
envFrames(const bool _only_vol) const279 f_cnt_t InstrumentSoundShaping::envFrames( const bool _only_vol ) const
280 {
281 	f_cnt_t ret_val = m_envLfoParameters[Volume]->PAHD_Frames();
282 
283 	if( _only_vol == false )
284 	{
285 		for( int i = Volume+1; i < NumTargets; ++i )
286 		{
287 			if( m_envLfoParameters[i]->isUsed() &&
288 				m_envLfoParameters[i]->PAHD_Frames() > ret_val )
289 			{
290 				ret_val = m_envLfoParameters[i]->PAHD_Frames();
291 			}
292 		}
293 	}
294 	return ret_val;
295 }
296 
297 
298 
299 
releaseFrames() const300 f_cnt_t InstrumentSoundShaping::releaseFrames() const
301 {
302 	if( !m_instrumentTrack->instrument() )
303 	{
304 		return 0;
305 	}
306 
307 	f_cnt_t ret_val = m_instrumentTrack->instrument()->desiredReleaseFrames();
308 
309 	if( m_instrumentTrack->instrument()->flags().testFlag( Instrument::IsSingleStreamed ) )
310 	{
311 		return ret_val;
312 	}
313 
314 	if( m_envLfoParameters[Volume]->isUsed() )
315 	{
316 		return m_envLfoParameters[Volume]->releaseFrames();
317 	}
318 
319 	for( int i = Volume+1; i < NumTargets; ++i )
320 	{
321 		if( m_envLfoParameters[i]->isUsed() )
322 		{
323 			ret_val = qMax( ret_val, m_envLfoParameters[i]->releaseFrames() );
324 		}
325 	}
326 	return ret_val;
327 }
328 
329 
330 
331 
saveSettings(QDomDocument & _doc,QDomElement & _this)332 void InstrumentSoundShaping::saveSettings( QDomDocument & _doc, QDomElement & _this )
333 {
334 	m_filterModel.saveSettings( _doc, _this, "ftype" );
335 	m_filterCutModel.saveSettings( _doc, _this, "fcut" );
336 	m_filterResModel.saveSettings( _doc, _this, "fres" );
337 	m_filterEnabledModel.saveSettings( _doc, _this, "fwet" );
338 
339 	for( int i = 0; i < NumTargets; ++i )
340 	{
341 		m_envLfoParameters[i]->saveState( _doc, _this ).setTagName(
342 			m_envLfoParameters[i]->nodeName() +
343 				QString( targetNames[i][1] ).toLower() );
344 	}
345 }
346 
347 
348 
349 
loadSettings(const QDomElement & _this)350 void InstrumentSoundShaping::loadSettings( const QDomElement & _this )
351 {
352 	m_filterModel.loadSettings( _this, "ftype" );
353 	m_filterCutModel.loadSettings( _this, "fcut" );
354 	m_filterResModel.loadSettings( _this, "fres" );
355 	m_filterEnabledModel.loadSettings( _this, "fwet" );
356 
357 	QDomNode node = _this.firstChild();
358 	while( !node.isNull() )
359 	{
360 		if( node.isElement() )
361 		{
362 			for( int i = 0; i < NumTargets; ++i )
363 			{
364 				if( node.nodeName() ==
365 					m_envLfoParameters[i]->nodeName() +
366 					QString( targetNames[i][1] ).
367 								toLower() )
368 				{
369 					m_envLfoParameters[i]->restoreState( node.toElement() );
370 				}
371 			}
372 		}
373 		node = node.nextSibling();
374 	}
375 }
376 
377 
378 
379 
380 
381 
382