Mercurial > hg > svapp
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 |