summaryrefslogtreecommitdiffstats
path: root/src/engine/channelhandle.h
blob: baf350b7de0b8521c1827a80e7bee404c93db289 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#pragma once

#include <QHash>
#include <QString>
#include <QVarLengthArray>
#include <QtDebug>
#include <memory>

#include "util/assert.h"

// ChannelHandle defines a unique identifier for channels of audio in the engine
// (e.g. headphone output, master output, deck 1, microphone 3). Previously we
// used the group string of the channel in the engine to uniquely identify it
// and key associative containers (e.g. QMap, QHash) but the downside to this is
// that we waste a lot of callback time hashing and re-hashing the strings.
//
// To solve this problem we introduce ChannelHandle, a thin wrapper around an
// integer. As engine channels are registered they are assigned a ChannelHandle
// starting at 0 and incrementing. The benefit to this scheme is that the hash
// and equality of ChannelHandles are simple to calculate and a QVarLengthArray
// can be used to create a fast associative container backed by a simple array
// (since the keys are numbered [0, num_channels]).

/// A wrapper around an integer handle. Used to uniquely identify and refer to
/// channels (headphone output, master output, deck 1, microphone 4, etc.) while
/// avoiding slow QString comparisons incurred when using the group.
///
/// A helper class, ChannelHandleFactory, keeps a running count of handles that
/// have been assigned.
class ChannelHandle {
  public:
    ChannelHandle() : m_iHandle(-1) {
    }

    inline bool valid() const {
        return m_iHandle >= 0;
    }

    inline int handle() const {
        return m_iHandle;
    }

  private:
    ChannelHandle(int iHandle)
            : m_iHandle(iHandle) {
    }

    void setHandle(int iHandle) {
        m_iHandle = iHandle;
    }

    int m_iHandle;

    friend class ChannelHandleFactory;
};

inline bool operator==(const ChannelHandle& h1, const ChannelHandle& h2) {
    return h1.handle() == h2.handle();
}

inline bool operator!=(const ChannelHandle& h1, const ChannelHandle& h2) {
    return h1.handle() != h2.handle();
}

inline QDebug operator<<(QDebug stream, const ChannelHandle& h) {
    stream << "ChannelHandle(" << h.handle() << ")";
    return stream;
}

inline uint qHash(
        const ChannelHandle& handle,
        uint seed = 0) {
    return qHash(handle.handle(), seed);
}

// Convenience class that mimics QPair<ChannelHandle, QString> except with
// custom equality and hash methods that save the cost of touching the QString.
class ChannelHandleAndGroup {
  public:
    ChannelHandleAndGroup(const ChannelHandle& handle, const QString& name)
            : m_handle(handle),
              m_name(name) {
    }

    inline const QString& name() const {
        return m_name;
    }

    inline const ChannelHandle& handle() const {
        return m_handle;
    }

    const ChannelHandle m_handle;
    const QString m_name;
};

inline bool operator==(const ChannelHandleAndGroup& g1, const ChannelHandleAndGroup& g2) {
    return g1.handle() == g2.handle();
}

inline bool operator!=(const ChannelHandleAndGroup& g1, const ChannelHandleAndGroup& g2) {
    return g1.handle() != g2.handle();
}

inline QDebug operator<<(QDebug stream, const ChannelHandleAndGroup& g) {
    stream << "ChannelHandleAndGroup(" << g.name() << "," << g.handle() << ")";
    return stream;
}

inline uint qHash(
        const ChannelHandleAndGroup& handle_group,
        uint seed = 0) {
    return qHash(handle_group.handle(), seed);
}

// A helper class used by EngineMaster to assign ChannelHandles to channel group
// strings. Warning: ChannelHandles produced by different ChannelHandleFactory
// objects are not compatible and will produce incorrect results when compared,
// stored in the same container, etc. In practice we only use one instance in
// EngineMaster.
class ChannelHandleFactory {
  public:
    ChannelHandleFactory() : m_iNextHandle(0) {
    }

    ChannelHandle getOrCreateHandle(const QString& group) {
        ChannelHandle& handle = m_groupToHandle[group];
        if (!handle.valid()) {
            handle.setHandle(m_iNextHandle++);
            DEBUG_ASSERT(handle.valid());
            DEBUG_ASSERT(!m_handleToGroup.contains(handle));
            m_handleToGroup.insert(handle, group);
        }
        return handle;
    }

    ChannelHandle handleForGroup(const QString& group) const {
        return m_groupToHandle.value(group, ChannelHandle());
    }

    QString groupForHandle(const ChannelHandle& handle) const {
        return m_handleToGroup.value(handle, QString());
    }

  private:
    int m_iNextHandle;
    QHash<QString, ChannelHandle> m_groupToHandle;
    QHash<ChannelHandle, QString> m_handleToGroup;
};

typedef std::shared_ptr<ChannelHandleFactory> ChannelHandleFactoryPointer;

// An associative container mapping ChannelHandle to a template type T. Backed
// by a QVarLengthArray with ChannelHandleMap::kMaxExpectedGroups pre-allocated
// entries. Insertions are amortized O(1) time (if less than kMaxExpectedGroups
// exist then no allocation will occur -- insertion is a mere copy). Lookups are
// O(1) and quite fast -- a simple index into an array using the handle's
// integer value.
template <class T>
class ChannelHandleMap {
    static const int kMaxExpectedGroups = 256;
    typedef QVarLengthArray<T, kMaxExpectedGroups> container_type;
  public:
    typedef typename QVarLengthArray<T, kMaxExpectedGroups>::const_iterator const_iterator;
    typedef typename QVarLengthArray<T, kMaxExpectedGroups>::iterator iterator;

    const T& at(const ChannelHandle& handle) const {
        if (!handle.valid()) {
            return m_dummy;
        }
        return m_data.at(handle.handle());
    }

    void insert(const ChannelHandle& handle, const T& value) {
        if (!handle.valid()) {
            return;
        }

        int iHandle = handle.handle();
        maybeExpand(iHandle + 1);
        m_data[iHandle] = value;
    }

    T& operator[](const ChannelHandle& handle) {
        if (!handle.valid()) {
            return m_dummy;
        }
        int iHandle = handle.handle();
        maybeExpand(iHandle + 1);
        return m_data[iHandle];
    }

    void clear() {
        m_data.clear();
    }

    typename container_type::iterator begin() {
        return m_data.begin();
    }

    typename container_type::const_iterator begin() const {
        return m_data.begin();
    }

    typename container_type::iterator end() {
        return m_data.end();
    }

    typename container_type::const_iterator end() const {
        return m_data.end();
    }

  private:
    inline void maybeExpand(int iSize) {
        if (m_data.size() < iSize) {
            m_data.resize(iSize);
        }
    }
    container_type m_data;
    T m_dummy;
};