Chris@305
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@305
|
2
|
Chris@305
|
3 /*
|
Chris@305
|
4 Sonic Visualiser
|
Chris@305
|
5 An audio file viewer and annotation editor.
|
Chris@305
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@305
|
7 This file copyright 2006 Chris Cannam, 2006-2014 QMUL.
|
Chris@305
|
8
|
Chris@305
|
9 This program is free software; you can redistribute it and/or
|
Chris@305
|
10 modify it under the terms of the GNU General Public License as
|
Chris@305
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@305
|
12 License, or (at your option) any later version. See the file
|
Chris@305
|
13 COPYING included with this distribution for more information.
|
Chris@305
|
14 */
|
Chris@305
|
15
|
Chris@305
|
16 #include "ClipMixer.h"
|
Chris@305
|
17
|
Chris@305
|
18 #include <sndfile.h>
|
Chris@310
|
19 #include <cmath>
|
Chris@305
|
20
|
Chris@305
|
21 #include "base/Debug.h"
|
Chris@305
|
22
|
Chris@442
|
23 //#define DEBUG_CLIP_MIXER 1
|
Chris@442
|
24
|
Chris@436
|
25 ClipMixer::ClipMixer(int channels, sv_samplerate_t sampleRate, sv_frame_t blockSize) :
|
Chris@305
|
26 m_channels(channels),
|
Chris@305
|
27 m_sampleRate(sampleRate),
|
Chris@305
|
28 m_blockSize(blockSize),
|
Chris@407
|
29 m_clipData(0),
|
Chris@407
|
30 m_clipLength(0),
|
Chris@407
|
31 m_clipF0(0),
|
Chris@407
|
32 m_clipRate(0)
|
Chris@305
|
33 {
|
Chris@305
|
34 }
|
Chris@305
|
35
|
Chris@305
|
36 ClipMixer::~ClipMixer()
|
Chris@305
|
37 {
|
Chris@317
|
38 if (m_clipData) free(m_clipData);
|
Chris@305
|
39 }
|
Chris@305
|
40
|
Chris@308
|
41 void
|
Chris@308
|
42 ClipMixer::setChannelCount(int channels)
|
Chris@308
|
43 {
|
Chris@308
|
44 m_channels = channels;
|
Chris@308
|
45 }
|
Chris@308
|
46
|
Chris@305
|
47 bool
|
Chris@436
|
48 ClipMixer::loadClipData(QString path, double f0, double level)
|
Chris@305
|
49 {
|
Chris@305
|
50 if (m_clipData) {
|
Chris@307
|
51 cerr << "ClipMixer::loadClipData: Already have clip loaded" << endl;
|
Chris@305
|
52 return false;
|
Chris@305
|
53 }
|
Chris@305
|
54
|
Chris@305
|
55 SF_INFO info;
|
Chris@305
|
56 SNDFILE *file;
|
Chris@305
|
57 float *tmpFrames;
|
Chris@436
|
58 sv_frame_t i;
|
Chris@305
|
59
|
Chris@305
|
60 info.format = 0;
|
Chris@305
|
61 file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info);
|
Chris@305
|
62 if (!file) {
|
Chris@595
|
63 cerr << "ClipMixer::loadClipData: Failed to open file path \""
|
Chris@308
|
64 << path << "\": " << sf_strerror(file) << endl;
|
Chris@595
|
65 return false;
|
Chris@305
|
66 }
|
Chris@305
|
67
|
Chris@305
|
68 tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float));
|
Chris@305
|
69 if (!tmpFrames) {
|
Chris@307
|
70 cerr << "ClipMixer::loadClipData: malloc(" << info.frames * info.channels * sizeof(float) << ") failed" << endl;
|
Chris@305
|
71 return false;
|
Chris@305
|
72 }
|
Chris@305
|
73
|
Chris@305
|
74 sf_readf_float(file, tmpFrames, info.frames);
|
Chris@305
|
75 sf_close(file);
|
Chris@305
|
76
|
Chris@305
|
77 m_clipData = (float *)malloc(info.frames * sizeof(float));
|
Chris@305
|
78 if (!m_clipData) {
|
Chris@307
|
79 cerr << "ClipMixer::loadClipData: malloc(" << info.frames * sizeof(float) << ") failed" << endl;
|
Chris@595
|
80 free(tmpFrames);
|
Chris@595
|
81 return false;
|
Chris@305
|
82 }
|
Chris@305
|
83
|
Chris@305
|
84 for (i = 0; i < info.frames; ++i) {
|
Chris@595
|
85 int j;
|
Chris@595
|
86 m_clipData[i] = 0.0f;
|
Chris@595
|
87 for (j = 0; j < info.channels; ++j) {
|
Chris@595
|
88 m_clipData[i] += tmpFrames[i * info.channels + j] * float(level);
|
Chris@595
|
89 }
|
Chris@305
|
90 }
|
Chris@305
|
91
|
Chris@305
|
92 free(tmpFrames);
|
Chris@305
|
93
|
Chris@305
|
94 m_clipLength = info.frames;
|
Chris@305
|
95 m_clipF0 = f0;
|
Chris@305
|
96 m_clipRate = info.samplerate;
|
Chris@310
|
97
|
Chris@310
|
98 return true;
|
Chris@305
|
99 }
|
Chris@307
|
100
|
Chris@307
|
101 void
|
Chris@308
|
102 ClipMixer::reset()
|
Chris@308
|
103 {
|
Chris@310
|
104 m_playing.clear();
|
Chris@310
|
105 }
|
Chris@310
|
106
|
Chris@436
|
107 double
|
Chris@436
|
108 ClipMixer::getResampleRatioFor(double frequency)
|
Chris@310
|
109 {
|
Chris@407
|
110 if (!m_clipData || !m_clipRate) return 1.0;
|
Chris@436
|
111 double pitchRatio = m_clipF0 / frequency;
|
Chris@436
|
112 double resampleRatio = m_sampleRate / m_clipRate;
|
Chris@310
|
113 return pitchRatio * resampleRatio;
|
Chris@310
|
114 }
|
Chris@310
|
115
|
Chris@436
|
116 sv_frame_t
|
Chris@436
|
117 ClipMixer::getResampledClipDuration(double frequency)
|
Chris@310
|
118 {
|
Chris@436
|
119 return sv_frame_t(ceil(double(m_clipLength) * getResampleRatioFor(frequency)));
|
Chris@308
|
120 }
|
Chris@308
|
121
|
Chris@308
|
122 void
|
Chris@307
|
123 ClipMixer::mix(float **toBuffers,
|
Chris@308
|
124 float gain,
|
Chris@307
|
125 std::vector<NoteStart> newNotes,
|
Chris@307
|
126 std::vector<NoteEnd> endingNotes)
|
Chris@307
|
127 {
|
Chris@310
|
128 foreach (NoteStart note, newNotes) {
|
matthiasm@321
|
129 if (note.frequency > 20 &&
|
matthiasm@322
|
130 note.frequency < 5000) {
|
matthiasm@321
|
131 m_playing.push_back(note);
|
matthiasm@321
|
132 }
|
Chris@310
|
133 }
|
Chris@310
|
134
|
Chris@310
|
135 std::vector<NoteStart> remaining;
|
Chris@310
|
136
|
Chris@310
|
137 float *levels = new float[m_channels];
|
Chris@310
|
138
|
Chris@397
|
139 #ifdef DEBUG_CLIP_MIXER
|
Chris@397
|
140 cerr << "ClipMixer::mix: have " << m_playing.size() << " playing note(s)"
|
Chris@397
|
141 << " and " << endingNotes.size() << " note(s) ending here"
|
Chris@397
|
142 << endl;
|
Chris@397
|
143 #endif
|
Chris@397
|
144
|
Chris@310
|
145 foreach (NoteStart note, m_playing) {
|
Chris@310
|
146
|
Chris@310
|
147 for (int c = 0; c < m_channels; ++c) {
|
Chris@345
|
148 levels[c] = note.level * gain;
|
Chris@310
|
149 }
|
Chris@310
|
150 if (note.pan != 0.0 && m_channels == 2) {
|
Chris@436
|
151 levels[0] *= 1.0f - note.pan;
|
Chris@436
|
152 levels[1] *= note.pan + 1.0f;
|
Chris@310
|
153 }
|
Chris@310
|
154
|
Chris@436
|
155 sv_frame_t start = note.frameOffset;
|
Chris@436
|
156 sv_frame_t durationHere = m_blockSize;
|
Chris@310
|
157 if (start > 0) durationHere = m_blockSize - start;
|
Chris@310
|
158
|
Chris@310
|
159 bool ending = false;
|
Chris@310
|
160
|
Chris@310
|
161 foreach (NoteEnd end, endingNotes) {
|
Chris@596
|
162 if (end.frequency == note.frequency &&
|
Chris@596
|
163 // This is > rather than >= because if we have a
|
Chris@596
|
164 // note-off and a note-on at the same time, the
|
Chris@596
|
165 // note-off must be switching off an earlier note-on,
|
Chris@596
|
166 // not the current one (zero-duration notes are
|
Chris@596
|
167 // forbidden earlier in the pipeline)
|
Chris@596
|
168 end.frameOffset > start &&
|
Chris@310
|
169 end.frameOffset <= m_blockSize) {
|
Chris@310
|
170 ending = true;
|
Chris@310
|
171 durationHere = end.frameOffset;
|
Chris@310
|
172 if (start > 0) durationHere = end.frameOffset - start;
|
Chris@310
|
173 break;
|
Chris@310
|
174 }
|
Chris@310
|
175 }
|
Chris@310
|
176
|
Chris@436
|
177 sv_frame_t clipDuration = getResampledClipDuration(note.frequency);
|
Chris@310
|
178 if (start + clipDuration > 0) {
|
Chris@310
|
179 if (start < 0 && start + clipDuration < durationHere) {
|
Chris@311
|
180 durationHere = start + clipDuration;
|
Chris@310
|
181 }
|
Chris@310
|
182 if (durationHere > 0) {
|
Chris@310
|
183 mixNote(toBuffers,
|
Chris@310
|
184 levels,
|
Chris@310
|
185 note.frequency,
|
Chris@310
|
186 start < 0 ? -start : 0,
|
Chris@310
|
187 start > 0 ? start : 0,
|
matthiasm@320
|
188 durationHere,
|
matthiasm@320
|
189 ending);
|
Chris@310
|
190 }
|
Chris@310
|
191 }
|
Chris@310
|
192
|
Chris@310
|
193 if (!ending) {
|
Chris@310
|
194 NoteStart adjusted = note;
|
Chris@310
|
195 adjusted.frameOffset -= m_blockSize;
|
Chris@310
|
196 remaining.push_back(adjusted);
|
Chris@310
|
197 }
|
Chris@310
|
198 }
|
Chris@310
|
199
|
Chris@313
|
200 delete[] levels;
|
Chris@313
|
201
|
Chris@310
|
202 m_playing = remaining;
|
Chris@307
|
203 }
|
Chris@307
|
204
|
Chris@310
|
205 void
|
Chris@310
|
206 ClipMixer::mixNote(float **toBuffers,
|
Chris@310
|
207 float *levels,
|
Chris@310
|
208 float frequency,
|
Chris@436
|
209 sv_frame_t sourceOffset,
|
Chris@436
|
210 sv_frame_t targetOffset,
|
Chris@436
|
211 sv_frame_t sampleCount,
|
matthiasm@320
|
212 bool isEnd)
|
Chris@310
|
213 {
|
Chris@310
|
214 if (!m_clipData) return;
|
Chris@310
|
215
|
Chris@436
|
216 double ratio = getResampleRatioFor(frequency);
|
Chris@310
|
217
|
Chris@436
|
218 double releaseTime = 0.01;
|
Chris@436
|
219 sv_frame_t releaseSampleCount = sv_frame_t(round(releaseTime * m_sampleRate));
|
matthiasm@320
|
220 if (releaseSampleCount > sampleCount) {
|
matthiasm@320
|
221 releaseSampleCount = sampleCount;
|
matthiasm@320
|
222 }
|
Chris@436
|
223 double releaseFraction = 1.0/double(releaseSampleCount);
|
Chris@310
|
224
|
Chris@436
|
225 for (sv_frame_t i = 0; i < sampleCount; ++i) {
|
Chris@310
|
226
|
Chris@436
|
227 sv_frame_t s = sourceOffset + i;
|
Chris@310
|
228
|
Chris@436
|
229 double os = double(s) / ratio;
|
Chris@436
|
230 sv_frame_t osi = sv_frame_t(floor(os));
|
Chris@310
|
231
|
Chris@310
|
232 //!!! just linear interpolation for now (same as SV's sample
|
Chris@310
|
233 //!!! player). a small sinc kernel would be better and
|
Chris@310
|
234 //!!! probably "good enough"
|
Chris@436
|
235 double value = 0.0;
|
Chris@310
|
236 if (osi < m_clipLength) {
|
Chris@310
|
237 value += m_clipData[osi];
|
Chris@310
|
238 }
|
Chris@310
|
239 if (osi + 1 < m_clipLength) {
|
Chris@436
|
240 value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - double(osi));
|
Chris@310
|
241 }
|
matthiasm@320
|
242
|
matthiasm@320
|
243 if (isEnd && i + releaseSampleCount > sampleCount) {
|
Chris@436
|
244 value *= releaseFraction * double(sampleCount - i); // linear ramp for release
|
matthiasm@320
|
245 }
|
matthiasm@320
|
246
|
Chris@310
|
247 for (int c = 0; c < m_channels; ++c) {
|
Chris@436
|
248 toBuffers[c][targetOffset + i] += float(levels[c] * value);
|
Chris@310
|
249 }
|
Chris@310
|
250 }
|
Chris@310
|
251 }
|
Chris@310
|
252
|
Chris@310
|
253
|