1 #include "sources/soundsourcewv.h"
2 
3 #include <wavpack.h>
4 
5 #include "util/logger.h"
6 
7 namespace mixxx {
8 
9 namespace {
10 
11 const Logger kLogger("SoundSourceWV");
12 
13 static WavpackStreamReader s_streamReader = {
14         SoundSourceWV::ReadBytesCallback,
15         SoundSourceWV::GetPosCallback,
16         SoundSourceWV::SetPosAbsCallback,
17         SoundSourceWV::SetPosRelCallback,
18         SoundSourceWV::PushBackByteCallback,
19         SoundSourceWV::GetlengthCallback,
20         SoundSourceWV::CanSeekCallback,
21         SoundSourceWV::WriteBytesCallback};
22 
23 } // anonymous namespace
24 
25 //static
26 const QString SoundSourceProviderWV::kDisplayName = QStringLiteral("WavPack");
27 
28 //static
29 const QStringList SoundSourceProviderWV::kSupportedFileExtensions = {
30         QStringLiteral("wv"),
31 };
32 
getPriorityHint(const QString & supportedFileExtension) const33 SoundSourceProviderPriority SoundSourceProviderWV::getPriorityHint(
34         const QString& supportedFileExtension) const {
35     Q_UNUSED(supportedFileExtension)
36     // This reference decoder is supposed to produce more accurate
37     // and reliable results than any other DEFAULT provider.
38     return SoundSourceProviderPriority::Higher;
39 }
40 
newSoundSource(const QUrl & url)41 SoundSourcePointer SoundSourceProviderWV::newSoundSource(const QUrl& url) {
42     return newSoundSourceFromUrl<SoundSourceWV>(url);
43 }
44 
SoundSourceWV(const QUrl & url)45 SoundSourceWV::SoundSourceWV(const QUrl& url)
46         : SoundSource(url),
47           m_wpc(nullptr),
48           m_sampleScaleFactor(CSAMPLE_ZERO),
49           m_pWVFile(nullptr),
50           m_pWVCFile(nullptr),
51           m_curFrameIndex(0) {
52 }
53 
~SoundSourceWV()54 SoundSourceWV::~SoundSourceWV() {
55     close();
56 }
57 
tryOpen(OpenMode,const OpenParams & params)58 SoundSource::OpenResult SoundSourceWV::tryOpen(
59         OpenMode /*mode*/,
60         const OpenParams& params) {
61     DEBUG_ASSERT(!m_wpc);
62     char msg[80]; // hold possible error message
63     int openFlags = OPEN_WVC | OPEN_NORMALIZE;
64     if ((params.getSignalInfo().getChannelCount() == 1) ||
65             (params.getSignalInfo().getChannelCount() == 2)) {
66         openFlags |= OPEN_2CH_MAX;
67     }
68 
69     // We use WavpackOpenFileInputEx to support Unicode paths on windows
70     // http://www.wavpack.com/lib_use.txt
71     QString wavPackFileName = getLocalFileName();
72     m_pWVFile = new QFile(wavPackFileName);
73     m_pWVFile->open(QFile::ReadOnly);
74     QString correctionFileName(wavPackFileName + "c");
75     if (QFile::exists(correctionFileName)) {
76         // If there is a correction file, open it as well
77         m_pWVCFile = new QFile(correctionFileName);
78         m_pWVCFile->open(QFile::ReadOnly);
79     }
80     m_wpc = WavpackOpenFileInputEx(&s_streamReader, m_pWVFile, m_pWVCFile, msg, openFlags, 0);
81     if (!m_wpc) {
82         kLogger.warning() << "failed to open file : " << msg;
83         return OpenResult::Failed;
84     }
85 
86     initChannelCountOnce(WavpackGetReducedChannels(static_cast<WavpackContext*>(m_wpc)));
87     initSampleRateOnce(WavpackGetSampleRate(static_cast<WavpackContext*>(m_wpc)));
88     initFrameIndexRangeOnce(
89             mixxx::IndexRange::forward(
90                     0,
91                     WavpackGetNumSamples(static_cast<WavpackContext*>(m_wpc))));
92 
93     if (WavpackGetMode(static_cast<WavpackContext*>(m_wpc)) & MODE_FLOAT) {
94         m_sampleScaleFactor = CSAMPLE_PEAK;
95     } else {
96         const int bitsPerSample = WavpackGetBitsPerSample(static_cast<WavpackContext*>(m_wpc));
97         if ((bitsPerSample >= 8) && (bitsPerSample <= 32)) {
98             // Range of signed sample values: [-2 ^ (bitsPerSample - 1), 2 ^ (bitsPerSample - 1) - 1]
99             const uint32_t absSamplePeak = 1u << (bitsPerSample - 1);
100             DEBUG_ASSERT(absSamplePeak > 0);
101             // Scaled range of sample values: [-CSAMPLE_PEAK, CSAMPLE_PEAK)
102             m_sampleScaleFactor = CSAMPLE_PEAK / absSamplePeak;
103         } else {
104             kLogger.warning()
105                     << "Invalid bits per sample:"
106                     << bitsPerSample;
107             return OpenResult::Aborted;
108         }
109     }
110 
111     m_curFrameIndex = frameIndexMin();
112 
113     return OpenResult::Succeeded;
114 }
115 
close()116 void SoundSourceWV::close() {
117     if (m_wpc) {
118         WavpackCloseFile(static_cast<WavpackContext*>(m_wpc));
119         m_wpc = nullptr;
120     }
121     if (m_pWVFile) {
122         m_pWVFile->close();
123         delete m_pWVFile;
124         m_pWVFile = nullptr;
125     }
126     if (m_pWVCFile) {
127         m_pWVCFile->close();
128         delete m_pWVCFile;
129         m_pWVCFile = nullptr;
130     }
131 }
132 
readSampleFramesClamped(const WritableSampleFrames & writableSampleFrames)133 ReadableSampleFrames SoundSourceWV::readSampleFramesClamped(
134         const WritableSampleFrames& writableSampleFrames) {
135     const SINT firstFrameIndex = writableSampleFrames.frameIndexRange().start();
136 
137     if (m_curFrameIndex != firstFrameIndex) {
138         if (WavpackSeekSample(static_cast<WavpackContext*>(m_wpc), firstFrameIndex)) {
139             m_curFrameIndex = firstFrameIndex;
140         } else {
141             kLogger.warning()
142                     << "Could not seek to first frame index"
143                     << firstFrameIndex;
144             m_curFrameIndex = WavpackGetSampleIndex(static_cast<WavpackContext*>(m_wpc));
145             return ReadableSampleFrames(IndexRange::between(m_curFrameIndex, m_curFrameIndex));
146         }
147     }
148     DEBUG_ASSERT(m_curFrameIndex == firstFrameIndex);
149 
150     const SINT numberOfFramesTotal = writableSampleFrames.frameLength();
151 
152     static_assert(sizeof(CSAMPLE) == sizeof(int32_t),
153             "CSAMPLE and int32_t must have the same size");
154     CSAMPLE* pOutputBuffer = writableSampleFrames.writableData();
155     SINT unpackCount = WavpackUnpackSamples(static_cast<WavpackContext*>(m_wpc),
156             reinterpret_cast<int32_t*>(pOutputBuffer),
157             numberOfFramesTotal);
158     DEBUG_ASSERT(unpackCount >= 0);
159     DEBUG_ASSERT(unpackCount <= numberOfFramesTotal);
160     if (!(WavpackGetMode(static_cast<WavpackContext*>(m_wpc)) & MODE_FLOAT)) {
161         // signed integer -> float
162         const SINT sampleCount = getSignalInfo().frames2samples(unpackCount);
163         for (SINT i = 0; i < sampleCount; ++i) {
164             const int32_t sampleValue =
165                     *reinterpret_cast<int32_t*>(pOutputBuffer);
166             *pOutputBuffer++ = CSAMPLE(sampleValue) * m_sampleScaleFactor;
167         }
168     }
169     const auto resultRange = IndexRange::forward(m_curFrameIndex, unpackCount);
170     m_curFrameIndex += unpackCount;
171     return ReadableSampleFrames(
172             resultRange,
173             SampleBuffer::ReadableSlice(
174                     writableSampleFrames.writableData(),
175                     getSignalInfo().frames2samples(unpackCount)));
176 }
177 
178 //static
ReadBytesCallback(void * id,void * data,int bcount)179 int32_t SoundSourceWV::ReadBytesCallback(void* id, void* data, int bcount) {
180     QFile* pFile = static_cast<QFile*>(id);
181     if (!pFile) {
182         return 0;
183     }
184     return pFile->read((char*)data, bcount);
185 }
186 
187 // static
GetPosCallback(void * id)188 uint32_t SoundSourceWV::GetPosCallback(void* id) {
189     QFile* pFile = static_cast<QFile*>(id);
190     if (!pFile) {
191         return 0;
192     }
193     return pFile->pos();
194 }
195 
196 //static
SetPosAbsCallback(void * id,unsigned int pos)197 int SoundSourceWV::SetPosAbsCallback(void* id, unsigned int pos) {
198     QFile* pFile = static_cast<QFile*>(id);
199     if (!pFile) {
200         return 0;
201     }
202     return pFile->seek(pos) ? 0 : -1;
203 }
204 
205 //static
SetPosRelCallback(void * id,int delta,int mode)206 int SoundSourceWV::SetPosRelCallback(void* id, int delta, int mode) {
207     QFile* pFile = static_cast<QFile*>(id);
208     if (!pFile) {
209         return 0;
210     }
211 
212     switch (mode) {
213     case SEEK_SET:
214         return pFile->seek(delta) ? 0 : -1;
215     case SEEK_CUR:
216         return pFile->seek(pFile->pos() + delta) ? 0 : -1;
217     case SEEK_END:
218         return pFile->seek(pFile->size() + delta) ? 0 : -1;
219     default:
220         return -1;
221     }
222 }
223 
224 //static
PushBackByteCallback(void * id,int c)225 int SoundSourceWV::PushBackByteCallback(void* id, int c) {
226     QFile* pFile = static_cast<QFile*>(id);
227     if (!pFile) {
228         return 0;
229     }
230     pFile->ungetChar((char)c);
231     return 1;
232 }
233 
234 //static
GetlengthCallback(void * id)235 uint32_t SoundSourceWV::GetlengthCallback(void* id) {
236     QFile* pFile = static_cast<QFile*>(id);
237     if (!pFile) {
238         return 0;
239     }
240     return pFile->size();
241 }
242 
243 //static
CanSeekCallback(void * id)244 int SoundSourceWV::CanSeekCallback(void* id) {
245     QFile* pFile = static_cast<QFile*>(id);
246     if (!pFile) {
247         return 0;
248     }
249     return pFile->isSequential() ? 0 : 1;
250 }
251 
252 //static
WriteBytesCallback(void * id,void * data,int32_t bcount)253 int32_t SoundSourceWV::WriteBytesCallback(void* id, void* data, int32_t bcount) {
254     QFile* pFile = static_cast<QFile*>(id);
255     if (!pFile) {
256         return 0;
257     }
258     return (int32_t)pFile->write((char*)data, bcount);
259 }
260 
261 } // namespace mixxx
262