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@305
|
23 ClipMixer::ClipMixer(int channels, int sampleRate, int blockSize) :
|
Chris@305
|
24 m_channels(channels),
|
Chris@305
|
25 m_sampleRate(sampleRate),
|
Chris@305
|
26 m_blockSize(blockSize),
|
Chris@305
|
27 m_clipData(0)
|
Chris@305
|
28 {
|
Chris@305
|
29 }
|
Chris@305
|
30
|
Chris@305
|
31 ClipMixer::~ClipMixer()
|
Chris@305
|
32 {
|
Chris@317
|
33 if (m_clipData) free(m_clipData);
|
Chris@305
|
34 }
|
Chris@305
|
35
|
Chris@308
|
36 void
|
Chris@308
|
37 ClipMixer::setChannelCount(int channels)
|
Chris@308
|
38 {
|
Chris@308
|
39 m_channels = channels;
|
Chris@308
|
40 }
|
Chris@308
|
41
|
Chris@305
|
42 bool
|
Chris@305
|
43 ClipMixer::loadClipData(QString path, float f0)
|
Chris@305
|
44 {
|
Chris@305
|
45 if (m_clipData) {
|
Chris@307
|
46 cerr << "ClipMixer::loadClipData: Already have clip loaded" << endl;
|
Chris@305
|
47 return false;
|
Chris@305
|
48 }
|
Chris@305
|
49
|
Chris@305
|
50 SF_INFO info;
|
Chris@305
|
51 SNDFILE *file;
|
Chris@305
|
52 int sampleCount = 0;
|
Chris@305
|
53 float *tmpFrames;
|
Chris@305
|
54 size_t i;
|
Chris@305
|
55
|
Chris@305
|
56 info.format = 0;
|
Chris@305
|
57 file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info);
|
Chris@305
|
58 if (!file) {
|
Chris@308
|
59 cerr << "ClipMixer::loadClipData: Failed to open file path \""
|
Chris@308
|
60 << path << "\": " << sf_strerror(file) << endl;
|
Chris@305
|
61 return false;
|
Chris@305
|
62 }
|
Chris@305
|
63
|
Chris@305
|
64 tmpFrames = (float *)malloc(info.frames * info.channels * sizeof(float));
|
Chris@305
|
65 if (!tmpFrames) {
|
Chris@307
|
66 cerr << "ClipMixer::loadClipData: malloc(" << info.frames * info.channels * sizeof(float) << ") failed" << endl;
|
Chris@305
|
67 return false;
|
Chris@305
|
68 }
|
Chris@305
|
69
|
Chris@305
|
70 sf_readf_float(file, tmpFrames, info.frames);
|
Chris@305
|
71 sf_close(file);
|
Chris@305
|
72
|
Chris@305
|
73 m_clipData = (float *)malloc(info.frames * sizeof(float));
|
Chris@305
|
74 if (!m_clipData) {
|
Chris@307
|
75 cerr << "ClipMixer::loadClipData: malloc(" << info.frames * sizeof(float) << ") failed" << endl;
|
Chris@305
|
76 free(tmpFrames);
|
Chris@305
|
77 return false;
|
Chris@305
|
78 }
|
Chris@305
|
79
|
Chris@305
|
80 for (i = 0; i < info.frames; ++i) {
|
Chris@305
|
81 int j;
|
Chris@305
|
82 m_clipData[i] = 0.0f;
|
Chris@305
|
83 for (j = 0; j < info.channels; ++j) {
|
Chris@305
|
84 m_clipData[i] += tmpFrames[i * info.channels + j];
|
Chris@305
|
85 }
|
Chris@305
|
86 }
|
Chris@305
|
87
|
Chris@305
|
88 free(tmpFrames);
|
Chris@305
|
89
|
Chris@305
|
90 m_clipLength = info.frames;
|
Chris@305
|
91 m_clipF0 = f0;
|
Chris@305
|
92 m_clipRate = info.samplerate;
|
Chris@310
|
93
|
Chris@310
|
94 return true;
|
Chris@305
|
95 }
|
Chris@307
|
96
|
Chris@307
|
97 void
|
Chris@308
|
98 ClipMixer::reset()
|
Chris@308
|
99 {
|
Chris@310
|
100 m_playing.clear();
|
Chris@310
|
101 }
|
Chris@310
|
102
|
Chris@310
|
103 float
|
Chris@310
|
104 ClipMixer::getResampleRatioFor(float frequency)
|
Chris@310
|
105 {
|
Chris@310
|
106 if (!m_clipData) return 1.0;
|
Chris@311
|
107 float pitchRatio = m_clipF0 / frequency;
|
Chris@310
|
108 float resampleRatio = m_sampleRate / m_clipRate;
|
Chris@310
|
109 return pitchRatio * resampleRatio;
|
Chris@310
|
110 }
|
Chris@310
|
111
|
Chris@310
|
112 int
|
Chris@310
|
113 ClipMixer::getResampledClipDuration(float frequency)
|
Chris@310
|
114 {
|
Chris@310
|
115 return int(ceil(m_clipLength * getResampleRatioFor(frequency)));
|
Chris@308
|
116 }
|
Chris@308
|
117
|
Chris@308
|
118 void
|
Chris@307
|
119 ClipMixer::mix(float **toBuffers,
|
Chris@308
|
120 float gain,
|
Chris@307
|
121 std::vector<NoteStart> newNotes,
|
Chris@307
|
122 std::vector<NoteEnd> endingNotes)
|
Chris@307
|
123 {
|
Chris@310
|
124 foreach (NoteStart note, newNotes) {
|
Chris@310
|
125 m_playing.push_back(note);
|
Chris@310
|
126 }
|
Chris@310
|
127
|
Chris@310
|
128 std::vector<NoteStart> remaining;
|
Chris@310
|
129
|
Chris@310
|
130 float *levels = new float[m_channels];
|
Chris@310
|
131
|
Chris@310
|
132 foreach (NoteStart note, m_playing) {
|
Chris@310
|
133
|
Chris@310
|
134 for (int c = 0; c < m_channels; ++c) {
|
Chris@310
|
135 levels[c] = gain;
|
Chris@310
|
136 }
|
Chris@310
|
137 if (note.pan != 0.0 && m_channels == 2) {
|
Chris@310
|
138 levels[0] *= 1.0 - note.pan;
|
Chris@310
|
139 levels[1] *= note.pan + 1.0;
|
Chris@310
|
140 }
|
Chris@310
|
141
|
Chris@310
|
142 int start = note.frameOffset;
|
Chris@310
|
143 int durationHere = m_blockSize;
|
Chris@310
|
144 if (start > 0) durationHere = m_blockSize - start;
|
Chris@310
|
145
|
Chris@310
|
146 bool ending = false;
|
Chris@310
|
147
|
Chris@310
|
148 foreach (NoteEnd end, endingNotes) {
|
Chris@310
|
149 if (end.frequency == note.frequency &&
|
Chris@310
|
150 end.frameOffset >= start &&
|
Chris@310
|
151 end.frameOffset <= m_blockSize) {
|
Chris@310
|
152 ending = true;
|
Chris@310
|
153 durationHere = end.frameOffset;
|
Chris@310
|
154 if (start > 0) durationHere = end.frameOffset - start;
|
Chris@310
|
155 break;
|
Chris@310
|
156 }
|
Chris@310
|
157 }
|
Chris@310
|
158
|
Chris@310
|
159 int clipDuration = getResampledClipDuration(note.frequency);
|
Chris@310
|
160 if (start + clipDuration > 0) {
|
Chris@310
|
161 if (start < 0 && start + clipDuration < durationHere) {
|
Chris@311
|
162 durationHere = start + clipDuration;
|
Chris@310
|
163 }
|
Chris@310
|
164 if (durationHere > 0) {
|
Chris@310
|
165 mixNote(toBuffers,
|
Chris@310
|
166 levels,
|
Chris@310
|
167 note.frequency,
|
Chris@310
|
168 start < 0 ? -start : 0,
|
Chris@310
|
169 start > 0 ? start : 0,
|
matthiasm@320
|
170 durationHere,
|
matthiasm@320
|
171 ending);
|
Chris@310
|
172 }
|
Chris@310
|
173 }
|
Chris@310
|
174
|
Chris@310
|
175 if (!ending) {
|
Chris@310
|
176 NoteStart adjusted = note;
|
Chris@310
|
177 adjusted.frameOffset -= m_blockSize;
|
Chris@310
|
178 remaining.push_back(adjusted);
|
Chris@310
|
179 }
|
Chris@310
|
180 }
|
Chris@310
|
181
|
Chris@313
|
182 delete[] levels;
|
Chris@313
|
183
|
Chris@310
|
184 m_playing = remaining;
|
Chris@307
|
185 }
|
Chris@307
|
186
|
Chris@310
|
187 void
|
Chris@310
|
188 ClipMixer::mixNote(float **toBuffers,
|
Chris@310
|
189 float *levels,
|
Chris@310
|
190 float frequency,
|
Chris@310
|
191 int sourceOffset,
|
Chris@310
|
192 int targetOffset,
|
matthiasm@320
|
193 int sampleCount,
|
matthiasm@320
|
194 bool isEnd)
|
Chris@310
|
195 {
|
Chris@310
|
196 if (!m_clipData) return;
|
Chris@310
|
197
|
Chris@310
|
198 float ratio = getResampleRatioFor(frequency);
|
Chris@310
|
199
|
matthiasm@320
|
200 float releaseTime = 0.01;
|
matthiasm@320
|
201 int releaseSampleCount = round(releaseTime * m_sampleRate);
|
matthiasm@320
|
202 if (releaseSampleCount > sampleCount) {
|
matthiasm@320
|
203 releaseSampleCount = sampleCount;
|
matthiasm@320
|
204 }
|
matthiasm@320
|
205 float releaseFraction = 1.f/releaseSampleCount;
|
Chris@310
|
206
|
Chris@310
|
207 for (int i = 0; i < sampleCount; ++i) {
|
Chris@310
|
208
|
Chris@310
|
209 int s = sourceOffset + i;
|
Chris@310
|
210
|
Chris@310
|
211 float os = s / ratio;
|
Chris@310
|
212 int osi = int(floor(os));
|
Chris@310
|
213
|
Chris@310
|
214 //!!! just linear interpolation for now (same as SV's sample
|
Chris@310
|
215 //!!! player). a small sinc kernel would be better and
|
Chris@310
|
216 //!!! probably "good enough"
|
Chris@310
|
217 float value = 0.f;
|
Chris@310
|
218 if (osi < m_clipLength) {
|
Chris@310
|
219 value += m_clipData[osi];
|
Chris@310
|
220 }
|
Chris@310
|
221 if (osi + 1 < m_clipLength) {
|
Chris@310
|
222 value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - osi);
|
Chris@310
|
223 }
|
matthiasm@320
|
224
|
matthiasm@320
|
225 if (isEnd && i + releaseSampleCount > sampleCount) {
|
matthiasm@320
|
226 value *= releaseFraction * (sampleCount - i); // linear ramp for release
|
matthiasm@320
|
227 }
|
matthiasm@320
|
228
|
Chris@310
|
229 for (int c = 0; c < m_channels; ++c) {
|
Chris@311
|
230 toBuffers[c][targetOffset + i] += levels[c] * value;
|
Chris@310
|
231 }
|
Chris@310
|
232 }
|
Chris@310
|
233 }
|
Chris@310
|
234
|
Chris@310
|
235
|