comparison audio/ClipMixer.cpp @ 468:56acd9368532 bqaudioio

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