1 #pragma once
2 // ChannelHandle defines a unique identifier for channels of audio in the engine
3 // (e.g. headphone output, master output, deck 1, microphone 3). Previously we
4 // used the group string of the channel in the engine to uniquely identify it
5 // and key associative containers (e.g. QMap, QHash) but the downside to this is
6 // that we waste a lot of callback time hashing and re-hashing the strings.
7 //
8 // To solve this problem we introduce ChannelHandle, a thin wrapper around an
9 // integer. As engine channels are registered they are assigned a ChannelHandle
10 // starting at 0 and incrementing. The benefit to this scheme is that the hash
11 // and equality of ChannelHandles are simple to calculate and a QVarLengthArray
12 // can be used to create a fast associative container backed by a simple array
13 // (since the keys are numbered [0, num_channels]).
14 //
15 // A helper class, ChannelHandleFactory, keeps a running count of handles that
16 // have been assigned.
17 
18 #include <QHash>
19 #include <QString>
20 #include <QVarLengthArray>
21 #include <QtDebug>
22 #include <memory>
23 
24 #include "util/assert.h"
25 
26 // A wrapper around an integer handle. Used to uniquely identify and refer to
27 // channels (headphone output, master output, deck 1, microphone 4, etc.) of
28 // audio in the engine.
29 class ChannelHandle {
30   public:
ChannelHandle()31     ChannelHandle() : m_iHandle(-1) {
32     }
33 
valid()34     inline bool valid() const {
35         return m_iHandle >= 0;
36     }
37 
handle()38     inline int handle() const {
39         return m_iHandle;
40     }
41 
42   private:
ChannelHandle(int iHandle)43     ChannelHandle(int iHandle)
44             : m_iHandle(iHandle) {
45     }
46 
setHandle(int iHandle)47     void setHandle(int iHandle) {
48         m_iHandle = iHandle;
49     }
50 
51     int m_iHandle;
52 
53     friend class ChannelHandleFactory;
54 };
55 
56 inline bool operator==(const ChannelHandle& h1, const ChannelHandle& h2) {
57     return h1.handle() == h2.handle();
58 }
59 
60 inline bool operator!=(const ChannelHandle& h1, const ChannelHandle& h2) {
61     return h1.handle() != h2.handle();
62 }
63 
64 inline QDebug operator<<(QDebug stream, const ChannelHandle& h) {
65     stream << "ChannelHandle(" << h.handle() << ")";
66     return stream;
67 }
68 
qHash(const ChannelHandle & handle)69 inline uint qHash(const ChannelHandle& handle) {
70     return qHash(handle.handle());
71 }
72 
73 // Convenience class that mimics QPair<ChannelHandle, QString> except with
74 // custom equality and hash methods that save the cost of touching the QString.
75 class ChannelHandleAndGroup {
76   public:
ChannelHandleAndGroup(const ChannelHandle & handle,const QString & name)77     ChannelHandleAndGroup(const ChannelHandle& handle, const QString& name)
78             : m_handle(handle),
79               m_name(name) {
80     }
81 
name()82     inline const QString& name() const {
83         return m_name;
84     }
85 
handle()86     inline const ChannelHandle& handle() const {
87         return m_handle;
88     }
89 
90     const ChannelHandle m_handle;
91     const QString m_name;
92 };
93 
94 inline bool operator==(const ChannelHandleAndGroup& g1, const ChannelHandleAndGroup& g2) {
95     return g1.handle() == g2.handle();
96 }
97 
98 inline bool operator!=(const ChannelHandleAndGroup& g1, const ChannelHandleAndGroup& g2) {
99     return g1.handle() != g2.handle();
100 }
101 
102 inline QDebug operator<<(QDebug stream, const ChannelHandleAndGroup& g) {
103     stream << "ChannelHandleAndGroup(" << g.name() << "," << g.handle() << ")";
104     return stream;
105 }
106 
qHash(const ChannelHandleAndGroup & handle_group)107 inline uint qHash(const ChannelHandleAndGroup& handle_group) {
108     return qHash(handle_group.handle());
109 }
110 
111 // A helper class used by EngineMaster to assign ChannelHandles to channel group
112 // strings. Warning: ChannelHandles produced by different ChannelHandleFactory
113 // objects are not compatible and will produce incorrect results when compared,
114 // stored in the same container, etc. In practice we only use one instance in
115 // EngineMaster.
116 class ChannelHandleFactory {
117   public:
ChannelHandleFactory()118     ChannelHandleFactory() : m_iNextHandle(0) {
119     }
120 
getOrCreateHandle(const QString & group)121     ChannelHandle getOrCreateHandle(const QString& group) {
122         ChannelHandle& handle = m_groupToHandle[group];
123         if (!handle.valid()) {
124             handle.setHandle(m_iNextHandle++);
125             DEBUG_ASSERT(handle.valid());
126             DEBUG_ASSERT(!m_handleToGroup.contains(handle));
127             m_handleToGroup.insert(handle, group);
128         }
129         return handle;
130     }
131 
handleForGroup(const QString & group)132     ChannelHandle handleForGroup(const QString& group) const {
133         return m_groupToHandle.value(group, ChannelHandle());
134     }
135 
groupForHandle(const ChannelHandle & handle)136     QString groupForHandle(const ChannelHandle& handle) const {
137         return m_handleToGroup.value(handle, QString());
138     }
139 
140   private:
141     int m_iNextHandle;
142     QHash<QString, ChannelHandle> m_groupToHandle;
143     QHash<ChannelHandle, QString> m_handleToGroup;
144 };
145 
146 typedef std::shared_ptr<ChannelHandleFactory> ChannelHandleFactoryPointer;
147 
148 // An associative container mapping ChannelHandle to a template type T. Backed
149 // by a QVarLengthArray with ChannelHandleMap::kMaxExpectedGroups pre-allocated
150 // entries. Insertions are amortized O(1) time (if less than kMaxExpectedGroups
151 // exist then no allocation will occur -- insertion is a mere copy). Lookups are
152 // O(1) and quite fast -- a simple index into an array using the handle's
153 // integer value.
154 template <class T>
155 class ChannelHandleMap {
156     static const int kMaxExpectedGroups = 256;
157     typedef QVarLengthArray<T, kMaxExpectedGroups> container_type;
158   public:
159     typedef typename QVarLengthArray<T, kMaxExpectedGroups>::const_iterator const_iterator;
160     typedef typename QVarLengthArray<T, kMaxExpectedGroups>::iterator iterator;
161 
ChannelHandleMap()162     ChannelHandleMap()
163             : m_dummy{} {
164     }
165 
at(const ChannelHandle & handle)166     const T& at(const ChannelHandle& handle) const {
167         if (!handle.valid()) {
168             return m_dummy;
169         }
170         return m_data.at(handle.handle());
171     }
172 
insert(const ChannelHandle & handle,const T & value)173     void insert(const ChannelHandle& handle, const T& value) {
174         if (!handle.valid()) {
175             return;
176         }
177 
178         int iHandle = handle.handle();
179         maybeExpand(iHandle + 1);
180         m_data[iHandle] = value;
181     }
182 
183     T& operator[](const ChannelHandle& handle) {
184         if (!handle.valid()) {
185             return m_dummy;
186         }
187         int iHandle = handle.handle();
188         maybeExpand(iHandle + 1);
189         return m_data[iHandle];
190     }
191 
clear()192     void clear() {
193         m_data.clear();
194     }
195 
begin()196     typename container_type::iterator begin() {
197         return m_data.begin();
198     }
199 
begin()200     typename container_type::const_iterator begin() const {
201         return m_data.begin();
202     }
203 
end()204     typename container_type::iterator end() {
205         return m_data.end();
206     }
207 
end()208     typename container_type::const_iterator end() const {
209         return m_data.end();
210     }
211 
212   private:
maybeExpand(int iSize)213     inline void maybeExpand(int iSize) {
214         if (QTypeInfo<T>::isComplex) {
215             // The value for complex types is initialized by QVarLengthArray
216             if (m_data.size() < iSize) {
217                 m_data.resize(iSize);
218             }
219         } else {
220             // We need to initialize simple types ourselves
221             while (m_data.size() < iSize) {
222                 m_data.append({});
223             }
224         }
225     }
226     container_type m_data;
227     T m_dummy;
228 };
229