1 | // Copyright (C) 2024 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #ifndef AUDIOGENERATIONUTILS_H |
5 | #define AUDIOGENERATIONUTILS_H |
6 | |
7 | |
8 | // |
9 | // W A R N I N G |
10 | // ------------- |
11 | // |
12 | // This file is not part of the Qt API. It exists purely as an |
13 | // implementation detail. This header file may change from version to |
14 | // version without notice, or even be removed. |
15 | // |
16 | // We mean it. |
17 | // |
18 | |
19 | #include <QAudioFormat> |
20 | #include <QAudioBuffer> |
21 | #include <chrono> |
22 | #include <limits> |
23 | |
24 | QT_BEGIN_NAMESPACE |
25 | |
26 | inline QByteArray createSineWaveData(const QAudioFormat &format, std::chrono::microseconds duration, |
27 | qint32 sampleIndex = 0, qreal frequency = 500, |
28 | qreal volume = 0.8) |
29 | { |
30 | if (!format.isValid()) |
31 | return {}; |
32 | |
33 | const qint32 length = format.bytesForDuration(microseconds: duration.count()); |
34 | |
35 | QByteArray data(format.bytesForDuration(microseconds: duration.count()), Qt::Uninitialized); |
36 | unsigned char *ptr = reinterpret_cast<unsigned char *>(data.data()); |
37 | const auto end = ptr + length; |
38 | |
39 | auto writeNextFrame = [&](auto value) { |
40 | Q_ASSERT(sizeof(value) == format.bytesPerSample()); |
41 | *reinterpret_cast<decltype(value) *>(ptr) = value; |
42 | ptr += sizeof(value); |
43 | }; |
44 | |
45 | for (; ptr < end; ++sampleIndex) { |
46 | const qreal x = sin(x: 2 * M_PI * frequency * sampleIndex / format.sampleRate()) * volume; |
47 | for (int ch = 0; ch < format.channelCount(); ++ch) { |
48 | switch (format.sampleFormat()) { |
49 | case QAudioFormat::UInt8: |
50 | writeNextFrame(static_cast<quint8>(std::round(x: (1.0 + x) / 2 * 255))); |
51 | break; |
52 | case QAudioFormat::Int16: |
53 | writeNextFrame( |
54 | static_cast<qint16>(std::round(x: x * std::numeric_limits<qint16>::max()))); |
55 | break; |
56 | case QAudioFormat::Int32: |
57 | writeNextFrame( |
58 | static_cast<qint32>(std::round(x: x * std::numeric_limits<qint32>::max()))); |
59 | break; |
60 | case QAudioFormat::Float: |
61 | writeNextFrame(static_cast<float>(x)); |
62 | break; |
63 | case QAudioFormat::Unknown: |
64 | case QAudioFormat::NSampleFormats: |
65 | break; |
66 | } |
67 | } |
68 | } |
69 | |
70 | Q_ASSERT(ptr == end); |
71 | |
72 | return data; |
73 | } |
74 | |
75 | class AudioGenerator : public QObject |
76 | { |
77 | Q_OBJECT |
78 | public: |
79 | AudioGenerator() |
80 | { |
81 | m_format.setSampleFormat(QAudioFormat::UInt8); |
82 | m_format.setSampleRate(8000); |
83 | m_format.setChannelConfig(QAudioFormat::ChannelConfigMono); |
84 | } |
85 | |
86 | void setFormat(const QAudioFormat &format) |
87 | { // |
88 | m_format = format; |
89 | } |
90 | |
91 | void setBufferCount(int count) |
92 | { // |
93 | m_maxBufferCount = std::max(a: count, b: 1); |
94 | } |
95 | |
96 | void setDuration(std::chrono::microseconds duration) |
97 | { // |
98 | m_duration = duration; |
99 | } |
100 | |
101 | void setFrequency(qreal frequency) |
102 | { // |
103 | m_frequency = frequency; |
104 | } |
105 | |
106 | void emitEmptyBufferOnStop() |
107 | { // |
108 | m_emitEmptyBufferOnStop = true; |
109 | } |
110 | |
111 | QAudioBuffer createAudioBuffer() |
112 | { |
113 | const std::chrono::microseconds bufferDuration = m_duration * (m_bufferIndex + 1) / m_maxBufferCount |
114 | - m_duration * m_bufferIndex / m_maxBufferCount; |
115 | QByteArray data = createSineWaveData(format: m_format, duration: bufferDuration, sampleIndex: m_sampleIndex, frequency: m_frequency); |
116 | Q_ASSERT(m_format.bytesPerSample()); |
117 | m_sampleIndex += data.size() / m_format.bytesPerSample(); |
118 | return QAudioBuffer(data, m_format); |
119 | } |
120 | |
121 | signals: |
122 | void done(); |
123 | void audioBufferCreated(const QAudioBuffer &buffer); |
124 | |
125 | public slots: |
126 | void nextBuffer() |
127 | { |
128 | if (m_bufferIndex == m_maxBufferCount) { |
129 | emit done(); |
130 | if (m_emitEmptyBufferOnStop) |
131 | emit audioBufferCreated(buffer: {}); |
132 | return; |
133 | } |
134 | |
135 | const QAudioBuffer buffer = createAudioBuffer(); |
136 | |
137 | emit audioBufferCreated(buffer); |
138 | ++m_bufferIndex; |
139 | } |
140 | |
141 | private: |
142 | int m_maxBufferCount = 1; |
143 | std::chrono::microseconds m_duration{ std::chrono::seconds{ 1 } }; |
144 | int m_bufferIndex = 0; |
145 | QAudioFormat m_format; |
146 | bool m_emitEmptyBufferOnStop = false; |
147 | qreal m_frequency = 500.; |
148 | qint32 m_sampleIndex = 0; |
149 | }; |
150 | |
151 | QT_END_NAMESPACE |
152 | |
153 | #endif // AUDIOGENERATIONUTILS_H |
154 | |