Mercurial > hg > svcore
comparison data/model/Labeller.h @ 305:fc656505c573
* Add labelling option for instants inserted through tapping (closes FR#1674184)
Needs some refinement still, but it's almost functionally complete
author | Chris Cannam |
---|---|
date | Mon, 08 Oct 2007 14:44:38 +0000 |
parents | |
children | 4669894a89ad |
comparison
equal
deleted
inserted
replaced
304:4fc6f49436b3 | 305:fc656505c573 |
---|---|
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-2007 Chris Cannam and 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 #ifndef _LABELLER_H_ | |
17 #define _LABELLER_H_ | |
18 | |
19 #include "SparseModel.h" | |
20 #include "SparseValueModel.h" | |
21 | |
22 #include "base/Selection.h" | |
23 | |
24 #include <QObject> | |
25 | |
26 #include <map> | |
27 #include <iostream> | |
28 | |
29 class Labeller : public QObject | |
30 { | |
31 public: | |
32 enum ValueType { | |
33 ValueNone, | |
34 ValueFromSimpleCounter, | |
35 ValueFromCyclicalCounter, | |
36 ValueFromTwoLevelCounter, | |
37 ValueFromFrameNumber, | |
38 ValueFromRealTime, | |
39 ValueFromRealTimeDifference, | |
40 ValueFromTempo, | |
41 ValueFromExistingNeighbour, | |
42 ValueFromLabel | |
43 }; | |
44 | |
45 // uses: | |
46 // | |
47 // 1. when adding points to a time-value model, generate values | |
48 // for those points based on their times or labels or a counter | |
49 // | |
50 // 2. when adding a single point to a time-instant model, generate | |
51 // a label for it based on its time and that of the previous point | |
52 // or a counter | |
53 // | |
54 // 3. when adding a single point to a time-instant model, generate | |
55 // a label for the previous point based on its time and that of | |
56 // the point just added (as tempo is based on time to the next | |
57 // point, not the previous one) | |
58 // | |
59 // 4. re-label a set of points that have already been added to a | |
60 // model | |
61 | |
62 Labeller(ValueType type) : | |
63 m_type(type), | |
64 m_counter(1), | |
65 m_counter2(1), | |
66 m_cycle(4), | |
67 m_dp(10), | |
68 m_rate(0) { } | |
69 | |
70 Labeller(const Labeller &l) : | |
71 QObject(), | |
72 m_type(l.m_type), | |
73 m_counter(1), | |
74 m_counter2(1), | |
75 m_cycle(l.m_cycle), | |
76 m_dp(l.m_dp), | |
77 m_rate(l.m_rate) { } | |
78 | |
79 virtual ~Labeller() { } | |
80 | |
81 typedef std::map<ValueType, QString> TypeNameMap; | |
82 TypeNameMap getTypeNames() const { | |
83 TypeNameMap m; | |
84 m[ValueNone] = tr("No numbering"); | |
85 m[ValueFromSimpleCounter] = tr("Simple counter starting from 1"); | |
86 m[ValueFromCyclicalCounter] = tr("Cyclical counter starting from 1"); | |
87 m[ValueFromTwoLevelCounter] = tr("Cyclical two-level counter (bar/beat)"); | |
88 m[ValueFromFrameNumber] = tr("Audio sample frame number"); | |
89 m[ValueFromRealTime] = tr("Time in seconds"); | |
90 m[ValueFromRealTimeDifference] = tr("Duration to the following item"); | |
91 m[ValueFromTempo] = tr("Tempo (bpm) based on duration to following item"); | |
92 m[ValueFromExistingNeighbour] = tr("Same value as the nearest previous item"); | |
93 m[ValueFromLabel] = tr("Value extracted from the item's label (where possible)"); | |
94 return m; | |
95 } | |
96 | |
97 ValueType getType() const { return m_type; } | |
98 void setType(ValueType type) { m_type = type; } | |
99 | |
100 int getCounterValue() const { return m_counter; } | |
101 void setCounterValue(int v) { m_counter = v; } | |
102 | |
103 int getSecondLevelCounterValue() const { return m_counter2; } | |
104 void setSecondLevelCounterValue(int v) { m_counter2 = v; } | |
105 | |
106 int getCounterCycleSize() const { return m_cycle; } | |
107 void setCounterCycleSize(int s) { | |
108 m_cycle = s; | |
109 m_dp = 1; | |
110 while (s > 0) { | |
111 s /= 10; | |
112 m_dp *= 10; | |
113 } | |
114 } | |
115 | |
116 void setSampleRate(float rate) { m_rate = rate; } | |
117 | |
118 void incrementCounter() { | |
119 m_counter++; | |
120 if (m_type == ValueFromCyclicalCounter || | |
121 m_type == ValueFromTwoLevelCounter) { | |
122 if (m_counter > m_cycle) { | |
123 m_counter = 1; | |
124 m_counter2++; | |
125 } | |
126 } | |
127 } | |
128 | |
129 template <typename PointType> | |
130 void label(PointType &newPoint, PointType *prevPoint = 0) { | |
131 if (m_type == ValueNone) { | |
132 newPoint.label = ""; | |
133 } else if (m_type == ValueFromTwoLevelCounter) { | |
134 newPoint.label = tr("%1.%2").arg(m_counter2).arg(m_counter); | |
135 incrementCounter(); | |
136 } else { | |
137 float value = getValueFor<PointType>(newPoint, prevPoint); | |
138 if (actingOnPrevPoint() && prevPoint) { | |
139 prevPoint->label = QString("%1").arg(value); | |
140 } else { | |
141 newPoint.label = QString("%1").arg(value); | |
142 } | |
143 } | |
144 } | |
145 | |
146 template <typename PointType> | |
147 void labelAll(SparseModel<PointType> &model, MultiSelection *ms) { | |
148 | |
149 typename SparseModel<PointType>::PointList::iterator i; | |
150 typename SparseModel<PointType>::PointList pl(model.getPoints()); | |
151 | |
152 typename SparseModel<PointType>::EditCommand *command = | |
153 new typename SparseModel<PointType>::EditCommand | |
154 (&model, tr("Label Points")); | |
155 | |
156 PointType prevPoint(0); | |
157 | |
158 for (i = pl.begin(); i != pl.end(); ++i) { | |
159 | |
160 bool inRange = true; | |
161 if (ms) { | |
162 Selection s(ms->getContainingSelection(i->frame, false)); | |
163 if (s.isEmpty() || !s.contains(i->frame)) { | |
164 inRange = false; | |
165 } | |
166 } | |
167 | |
168 PointType p(*i); | |
169 | |
170 if (!inRange) { | |
171 prevPoint = p; | |
172 continue; | |
173 } | |
174 | |
175 if (actingOnPrevPoint()) { | |
176 if (i != pl.begin()) { | |
177 command->deletePoint(prevPoint); | |
178 label<PointType>(p, &prevPoint); | |
179 command->addPoint(prevPoint); | |
180 } | |
181 } else { | |
182 command->deletePoint(p); | |
183 label<PointType>(p, &prevPoint); | |
184 command->addPoint(p); | |
185 } | |
186 | |
187 prevPoint = p; | |
188 } | |
189 | |
190 command->finish(); | |
191 } | |
192 | |
193 template <typename PointType> | |
194 void setValue(PointType &newPoint, PointType *prevPoint = 0) { | |
195 if (m_type == ValueFromExistingNeighbour) { | |
196 if (!prevPoint) { | |
197 std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl; | |
198 } else { | |
199 newPoint.value = prevPoint->value; | |
200 } | |
201 } else { | |
202 float value = getValueFor<PointType>(newPoint, prevPoint); | |
203 if (actingOnPrevPoint() && prevPoint) { | |
204 prevPoint->value = value; | |
205 } else { | |
206 newPoint.value = value; | |
207 } | |
208 } | |
209 } | |
210 | |
211 bool actingOnPrevPoint() const { | |
212 return (m_type == ValueFromRealTimeDifference || | |
213 m_type == ValueFromTempo); | |
214 } | |
215 | |
216 protected: | |
217 template <typename PointType> | |
218 float getValueFor(PointType &newPoint, PointType *prevPoint) | |
219 { | |
220 float value = 0.f; | |
221 | |
222 switch (m_type) { | |
223 | |
224 case ValueNone: | |
225 value = 0; | |
226 break; | |
227 | |
228 case ValueFromSimpleCounter: | |
229 case ValueFromCyclicalCounter: | |
230 value = m_counter; | |
231 incrementCounter(); | |
232 break; | |
233 | |
234 case ValueFromTwoLevelCounter: | |
235 value = m_counter2 + double(m_counter) / double(m_dp); | |
236 incrementCounter(); | |
237 break; | |
238 | |
239 case ValueFromFrameNumber: | |
240 value = newPoint.frame; | |
241 break; | |
242 | |
243 case ValueFromRealTime: | |
244 if (m_rate == 0.f) { | |
245 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; | |
246 } else { | |
247 value = float(newPoint.frame) / float(m_rate); | |
248 } | |
249 break; | |
250 | |
251 case ValueFromRealTimeDifference: | |
252 case ValueFromTempo: | |
253 if (m_rate == 0.f) { | |
254 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; | |
255 } else if (!prevPoint) { | |
256 std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl; | |
257 } else { | |
258 size_t f0 = prevPoint->frame, f1 = newPoint.frame; | |
259 if (m_type == ValueFromRealTimeDifference) { | |
260 value = float(f1 - f0) / m_rate; | |
261 } else { | |
262 if (f1 > f0) { | |
263 value = (60.f * m_rate) / (f1 - f0); | |
264 } | |
265 } | |
266 } | |
267 break; | |
268 | |
269 case ValueFromExistingNeighbour: | |
270 // need to deal with this in the calling function, as this | |
271 // function must handle points that don't have values to | |
272 // read from | |
273 break; | |
274 | |
275 case ValueFromLabel: | |
276 if (newPoint.label != "") { | |
277 // more forgiving than QString::toFloat() | |
278 value = atof(newPoint.label.toLocal8Bit()); | |
279 } else { | |
280 value = 0.f; | |
281 } | |
282 break; | |
283 } | |
284 | |
285 return value; | |
286 } | |
287 | |
288 ValueType m_type; | |
289 int m_counter; | |
290 int m_counter2; | |
291 int m_cycle; | |
292 int m_dp; | |
293 float m_rate; | |
294 }; | |
295 | |
296 #endif |