-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathResample.cpp
223 lines (192 loc) · 6.78 KB
/
Resample.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// Copyright Alexander Grigoriev, 1997-2002, All Rights Reserved
// Resample.cpp
#include "stdafx.h"
#include "WaveSoapFrontDoc.h"
#include "resource.h"
#include "Resample.h"
#include "MessageBoxSynch.h"
#define _USE_MATH_DEFINES // for M_PI definition
#include <math.h>
// The purpose of this class is to process data and, in case of output data clipped, ask if it's OK to proceed
class CResampleProcContext : public CWaveProcContext
{
typedef CWaveProcContext BaseClass;
typedef CResampleProcContext ThisClass;
public:
typedef std::auto_ptr<ThisClass> auto_ptr;
CResampleProcContext(CWaveSoapFrontDoc * pDoc,
UINT StatusStringId, UINT OperationNameId,
CWaveFile & SrcFile, CWaveFile & DstFile,
long NewSampleRate, bool KeepSamplesPerSec);
virtual void DeInit();
};
static unsigned long GreatestCommonFactor(unsigned long x1, unsigned long x2)
{
ASSERT(0 != x2);
ASSERT(0 != x1);
while (1)
{
unsigned long remainder = x1 % x2;
if (0 == remainder)
{
return x2;
}
x1 = x2;
x2 = remainder;
}
}
CResampleContext::CResampleContext(CWaveSoapFrontDoc * pDoc,
UINT StatusStringId, UINT OperationNameId,
CWaveFile & SrcFile, CWaveFile &DstFile,
long NewSampleRate, bool KeepSamplesPerSec)
: BaseClass(pDoc, StatusStringId, OperationNameId)
{
long InputSampleRate = SrcFile.SampleRate();
long common = GreatestCommonFactor(NewSampleRate, InputSampleRate);
long PreExpansionRate = NewSampleRate / common;
long PostDecimateRate = InputSampleRate / common;
if (PreExpansionRate > 6 || PostDecimateRate > 6)
{
// cannot use just decimation and expansion (de-decimation). This conversion will require fractional sliding interpolating filter
// make sure the filter 99% pass frequency is at or below 3/4*PI, which means the sample rate fraction is under or equal 3/4
PreExpansionRate = (NewSampleRate + InputSampleRate - 1) / InputSampleRate;
while (NewSampleRate * 4 > InputSampleRate * PreExpansionRate * 3)
{
PreExpansionRate += 1;
}
PostDecimateRate = InputSampleRate * PreExpansionRate / NewSampleRate;
while (NewSampleRate * 4 > InputSampleRate * PreExpansionRate * 3 / PostDecimateRate)
{
PostDecimateRate -= 1;
}
}
try
{
CWaveProcContext * pFirstPass = new CWaveProcContext(pDoc);
AddContext(pFirstPass);
if (PreExpansionRate != 1)
{
pFirstPass->AddWaveProc(new CDeDecimator(PreExpansionRate));
}
double AntiAliasCutoffFrequency;
if (NewSampleRate > InputSampleRate)
{
// upsampling, the antialiasing is based on the input frequency
AntiAliasCutoffFrequency = InputSampleRate / 2.;
}
else
{
// downsampling, the antialiasing is based on the output frequency
AntiAliasCutoffFrequency = NewSampleRate / 2.;
}
FilterCoefficients coeffs = { 0 };
LowpassFilter lpf;
double PassbandLoss = 0.997;
double StopbandLoss = 0.003; // -50 dB for each pass, -100 dB result
if (PostDecimateRate > 1
|| InputSampleRate * PreExpansionRate == NewSampleRate)
{
// will not use fractional resample filter, so can have more passband loss
// and need more stopband loss
// make sure we'll not see ghosts of aliases in FFT view
PassbandLoss = 0.999;
StopbandLoss = 0.001; // -60 dB for each pass, -120 dB result
}
lpf.CreateElliptic(AntiAliasCutoffFrequency * 0.99*2.*M_PI / (InputSampleRate * PreExpansionRate), PassbandLoss,
AntiAliasCutoffFrequency * 2.*M_PI / (InputSampleRate * PreExpansionRate), StopbandLoss);
lpf.GetCoefficients(coeffs.m_LpfCoeffs);
coeffs.m_nLpfOrder = lpf.GetFilterOrder();
// now add forward pass IIR to the temp file
CFilterProc * pFilter1 = new CFilterProc;
pFilter1->SetFilterCoefficients(coeffs);
pFirstPass->AddWaveProc(pFilter1);
CWaveFormat TempFileFormat;
// TODO: add post-roll
TempFileFormat.InitFormat(SampleTypeFloat32, InputSampleRate * PreExpansionRate, SrcFile.Channels());
NUMBER_OF_SAMPLES TempFileNumberOfSamples = SrcFile.NumberOfSamples() * PreExpansionRate;
CWaveFile TempFile;
if (!TempFile.CreateWaveFile(NULL, TempFileFormat, ALL_CHANNELS, TempFileNumberOfSamples,
CreateWaveFileDeleteAfterClose
| CreateWaveFileAllowMemoryFile
| CreateWaveFileTempDir
| CreateWaveFileTemp, NULL))
{
AfxMessageBox(IDS_UNABLE_TO_CREATE_TEMPORARY_FILE, MB_OK | MB_ICONEXCLAMATION);
throw std::bad_alloc();
}
pFirstPass->InitSource(SrcFile, 0, SrcFile.NumberOfSamples(), ALL_CHANNELS);
pFirstPass->InitDestination(TempFile, 0, TempFileNumberOfSamples /*+ 1000*/, ALL_CHANNELS, FALSE);
CWaveProcContext * pSecondPass = new CWaveProcContext(pDoc);
pSecondPass->m_NumberOfBackwardPasses = 1;
pSecondPass->m_NumberOfForwardPasses = 0;
AddContext(pSecondPass);
// now add backward pass IIR to the temp file
CFilterProc * pFilter2 = new CFilterProc;
pFilter2->SetFilterCoefficients(coeffs);
pSecondPass->AddWaveProc(pFilter2);
pSecondPass->InitSource(TempFile, 0, TempFileNumberOfSamples /*+ 1000*/, ALL_CHANNELS);
pSecondPass->InitDestination(DstFile, 0, NewSampleRate*LONGLONG(SrcFile.NumberOfSamples()) / InputSampleRate, ALL_CHANNELS, FALSE);
if (PostDecimateRate != 1)
{
pSecondPass->AddWaveProc(new CDecimator(PostDecimateRate));
}
if (InputSampleRate * PreExpansionRate != NewSampleRate * PostDecimateRate)
{
// fractional resample, add CResampleFilter
pSecondPass->AddWaveProc(new CResampleFilter(NewSampleRate, AntiAliasCutoffFrequency, KeepSamplesPerSec));
}
}
catch (std::bad_alloc)
{
RetireAllChildren();
throw;
}
}
CResampleProcContext::CResampleProcContext(CWaveSoapFrontDoc * pDoc,
UINT StatusStringId, UINT OperationNameId,
CWaveFile & SrcFile, CWaveFile &DstFile,
long NewSampleRate, bool KeepSamplesPerSec)
: BaseClass(pDoc, StatusStringId, OperationNameId)
{
InitSource(SrcFile, 0, LAST_SAMPLE, ALL_CHANNELS);
InitDestination(DstFile, 0, LAST_SAMPLE, ALL_CHANNELS, FALSE);
#if 0
FIXME AddWaveProc(new CResampleFilter(NewSampleRate, KeepSamplesPerSec));
#endif
}
void CResampleProcContext::DeInit()
{
BaseClass::DeInit();
// if overflow happened, ask the user if it should continue
if ( ! WasClipped())
{
return;
}
class QueryOverflow : public MainThreadCall
{
public:
QueryOverflow(CResampleProcContext * pContext)
: m_pContext(pContext)
{
}
protected:
virtual LRESULT Exec()
{
// bring document frame to the top, then return
CDocumentPopup pop(m_pContext->m_pDocument);
CString s;
s.Format(IDS_SOUND_CLIPPED, static_cast<LPCTSTR>(m_pContext->m_pDocument->GetTitle()),
int(m_pContext->GetMaxClipped() * (100. / 32678)));
CString s1;
s1.LoadString(IDS_CONTINUE_QUESTION);
s += s1;
LRESULT result = AfxMessageBox(s, MB_YESNO | MB_ICONEXCLAMATION);
return result;
}
CResampleProcContext * const m_pContext;
} call(this);
if (IDNO == call.Call())
{
m_Flags |= OperationContextStop;
}
}