Skip to content

Commit 35db02f

Browse files
authored
Merge pull request #170 from Sharganov/dev_playTone
implemented a tone generator
2 parents d9994a6 + 67913dc commit 35db02f

File tree

7 files changed

+351
-0
lines changed

7 files changed

+351
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/* Copyright 2016 Artem Sharganov and Iakov Kirilenko
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License. */
14+
15+
#include "audioSynthDevices.h"
16+
17+
#include <QtMultimedia/QAudioDeviceInfo>
18+
19+
AudioSynthDevice::AudioSynthDevice(QObject *parent, int sampleRate, int sampleSize)
20+
: QIODevice(parent)
21+
, mBuffer(0)
22+
, mPos(0)
23+
, mSampleRate(sampleRate)
24+
, mSampleSize(sampleSize)
25+
{
26+
27+
}
28+
29+
AudioSynthDevice::~AudioSynthDevice()
30+
{
31+
32+
}
33+
34+
void AudioSynthDevice::start(int hzFreq)
35+
{
36+
open(QIODevice::ReadOnly);
37+
if(mBuffered) {
38+
qint64 length = (mSampleRate * (mSampleSize / 8));
39+
mBuffer.resize(length);
40+
generate(mBuffer.data(), length, hzFreq);
41+
}
42+
else mHzFreq = hzFreq;
43+
44+
}
45+
46+
void AudioSynthDevice::stop()
47+
{
48+
newCall = true;
49+
mPos = 0;
50+
close();
51+
}
52+
53+
// Modefied coupled first-order form algorithm with fixed point arithmetic
54+
int AudioSynthDevice::generate(char *data, int length, int hzFreq)
55+
{
56+
const int channelBytes = mSampleSize / 8;
57+
58+
qint64 maxlen = length/channelBytes;
59+
60+
static const int M = 1 << 30;
61+
const auto w = hzFreq * M_PI / mSampleRate;
62+
const long long b1 = 2.0 * cos(w)*M;
63+
static const int AMPLITUDE = (1 << (mSampleSize - 1)) - 1;
64+
65+
unsigned char *ptr = reinterpret_cast<unsigned char *>(data);
66+
67+
68+
// Need to save values between readData(...) calls, so static
69+
static long long y0 = 0;
70+
static decltype(y0) y1 = 0;
71+
static decltype(y0) y2 = 0;
72+
73+
if(newCall)
74+
{
75+
y1 = M * std::sin(-w);
76+
y2 = M * std::sin(-2*w);
77+
newCall = false;
78+
}
79+
80+
int i = 0;
81+
82+
for(i = 0; i < maxlen; ++i){
83+
84+
y0 = b1*y1 / M - y2;
85+
y2 = b1*y0 / M - y1;
86+
y1 = b1*y2 / M - y0;
87+
88+
if(mSampleSize == 8) {
89+
const qint8 val = static_cast<qint8>(y0*AMPLITUDE/M);
90+
*reinterpret_cast<quint8*>(ptr) = val;
91+
}
92+
if(mSampleSize == 16) {
93+
const qint16 val = static_cast<qint16>(y0*AMPLITUDE/M);
94+
*reinterpret_cast<quint16*>(ptr) = val;
95+
}
96+
97+
ptr+=channelBytes;
98+
}
99+
100+
return i*channelBytes;
101+
}
102+
103+
qint64 AudioSynthDevice::readData(char *data, qint64 len)
104+
{
105+
if(mBuffered) {
106+
qint64 total = 0;
107+
while (len - total > 0) {
108+
const qint64 chunk = qMin((mBuffer.size() - mPos), len - total);
109+
memcpy(data + total, mBuffer.constData() + mPos, chunk);
110+
mPos = (mPos + chunk) % mBuffer.size();
111+
total += chunk;
112+
}
113+
return total;
114+
} else
115+
return generate(data, len, mHzFreq);
116+
}
117+
118+
qint64 AudioSynthDevice::writeData(const char *data, qint64 len)
119+
{
120+
Q_UNUSED(data);
121+
Q_UNUSED(len);
122+
123+
return 0;
124+
}
125+
126+
qint64 AudioSynthDevice::bytesAvailable() const
127+
{
128+
return mBuffer.size() + QIODevice::bytesAvailable();
129+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* Copyright 2016 Artem Sharganov
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License. */
14+
15+
#pragma once
16+
17+
#include <QtCore/QByteArray>
18+
#include <QtCore/QIODevice>
19+
#include <QtMultimedia/QAudioFormat>
20+
21+
/// QIODevice that synthesize sine wave values
22+
class AudioSynthDevice : public QIODevice
23+
{
24+
Q_OBJECT
25+
26+
public:
27+
/// Constructor
28+
AudioSynthDevice(QObject *parent, int sampleRate, int sampleSize);
29+
30+
~AudioSynthDevice();
31+
32+
/// Provides reading from device
33+
qint64 readData(char *data, qint64 maxlen);
34+
35+
/// Opens device, run generation in buffered mode
36+
void start(int hzFreq);
37+
38+
/// Close device and reset pose
39+
void stop();
40+
41+
/// Stub, because readonly device
42+
qint64 writeData(const char *data, qint64 len);
43+
44+
/// Returns amount of available bytes
45+
qint64 bytesAvailable() const;
46+
47+
private:
48+
/// Sythesize sine wave values
49+
int generate(char *data, int length, int hzFreq);
50+
51+
private:
52+
53+
/// Internal buffer, used in buffered mode
54+
QByteArray mBuffer;
55+
56+
qint64 mPos;
57+
58+
int mHzFreq;
59+
60+
const int mSampleRate;
61+
62+
const int mSampleSize;
63+
64+
/// Mode of device
65+
bool mBuffered = false;
66+
67+
/// New call of playTone(...), not readData(...) call
68+
bool newCall = true;
69+
};
70+

trikControl/src/brick.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "rangeSensor.h"
4545
#include "servoMotor.h"
4646
#include "soundSensor.h"
47+
#include "tonePlayer.h"
4748
#include "vectorSensor.h"
4849

4950
#include "mspBusAutoDetector.h"
@@ -71,6 +72,7 @@ Brick::Brick(const trikKernel::DifferentOwnerPointer<trikHal::HardwareAbstractio
7172
, const QString &modelConfig
7273
, const QString &mediaPath)
7374
: mHardwareAbstraction(hardwareAbstraction)
75+
, mTonePlayer(new TonePlayer())
7476
, mMediaPath(mediaPath)
7577
, mConfigurer(systemConfig, modelConfig)
7678
{
@@ -204,6 +206,22 @@ void Brick::playSound(const QString &soundFileName)
204206
}
205207
}
206208

209+
210+
void Brick::playTone(int hzFreq, int msDuration)
211+
{
212+
QLOG_INFO() << "Playing tone (" << hzFreq << "," << msDuration << ")";
213+
214+
if (msDuration < 10)
215+
return;
216+
if (hzFreq > 8000)
217+
return;
218+
if (hzFreq < 20)
219+
return;
220+
//mHardwareAbstraction->systemSound()->playTone(hzFreq, msDuration);
221+
//mTonePlayer->play(hzFreq, msDuration);
222+
QMetaObject::invokeMethod(mTonePlayer.data(), "play", Q_ARG(int, hzFreq), Q_ARG(int, msDuration));
223+
}
224+
207225
void Brick::say(const QString &text)
208226
{
209227
QStringList args{"-c", "espeak -v russian_test -s 100 \"" + text + "\""};

trikControl/src/brick.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class PowerMotor;
4747
class PwmCapture;
4848
class RangeSensor;
4949
class ServoMotor;
50+
class TonePlayer;
5051
class VectorSensor;
5152

5253
/// Class representing TRIK controller board and devices installed on it, also provides access
@@ -83,6 +84,8 @@ public slots:
8384

8485
void playSound(const QString &soundFileName) override;
8586

87+
void playTone(int hzFreq, int msDuration);
88+
8689
void say(const QString &text) override;
8790

8891
void stop() override;
@@ -154,6 +157,7 @@ public slots:
154157
QScopedPointer<Keys> mKeys;
155158
QScopedPointer<Display> mDisplay;
156159
QScopedPointer<Led> mLed;
160+
QScopedPointer<TonePlayer> mTonePlayer;
157161

158162
QHash<QString, ServoMotor *> mServoMotors; // Has ownership.
159163
QHash<QString, PwmCapture *> mPwmCaptures; // Has ownership.

trikControl/src/tonePlayer.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* Copyright 2016 Artem Sharganov
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License. */
14+
15+
16+
#include "tonePlayer.h"
17+
18+
19+
namespace trikControl{
20+
21+
TonePlayer::TonePlayer()
22+
{
23+
mTimer.setSingleShot(true);
24+
initializeAudio();
25+
mDevice = new AudioSynthDevice(this, mFormat.sampleRate(), mFormat.sampleSize());
26+
mOutput = new QAudioOutput(mFormat, this);
27+
}
28+
29+
void TonePlayer::initializeAudio()
30+
{
31+
32+
mFormat.setChannelCount(1);
33+
mFormat.setSampleRate(16000);
34+
mFormat.setSampleSize(16);
35+
mFormat.setSampleType(QAudioFormat::SampleType::SignedInt);
36+
mFormat.setCodec("audio/pcm");
37+
38+
connect(&mTimer, SIGNAL(timeout()), this, SLOT(stop()));
39+
40+
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
41+
if (!info.isFormatSupported(mFormat)) {
42+
mFormat = info.nearestFormat(mFormat);
43+
}
44+
}
45+
46+
void TonePlayer::play(int hzFreq, int msDuration)
47+
{
48+
mOutput->reset();
49+
switch (mOutput->state()) {
50+
case QAudio::IdleState: break;
51+
default:break;
52+
}
53+
54+
mTimer.setInterval(msDuration);
55+
mDevice->start(hzFreq);
56+
mTimer.start();
57+
mOutput->start(mDevice);
58+
}
59+
60+
void TonePlayer::stop()
61+
{
62+
mDevice->stop();
63+
mTimer.stop();
64+
mOutput->stop();
65+
66+
}
67+
}
68+
69+

trikControl/src/tonePlayer.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* Copyright 2016 Artem Sharganov
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License. */
14+
15+
#pragma once
16+
17+
#include "audioSynthDevices.h"
18+
19+
#include <QtCore/QObject>
20+
#include <QtCore/QTimer>
21+
#include <QtMultimedia/QAudioOutput>
22+
23+
24+
25+
namespace trikControl {
26+
27+
/// Tone player. Play tones
28+
class TonePlayer : public QObject
29+
{
30+
Q_OBJECT
31+
32+
public:
33+
34+
/// Constructor
35+
TonePlayer();
36+
37+
public slots:
38+
39+
/// Play sound
40+
void play(int freqHz, int durationMs);
41+
42+
private:
43+
QAudioFormat mFormat;
44+
45+
AudioSynthDevice *mDevice; // Has ownership.
46+
47+
QAudioOutput *mOutput; // Has ownership.
48+
49+
QTimer mTimer;
50+
51+
void initializeAudio();
52+
53+
public slots:
54+
void stop();
55+
};
56+
}

0 commit comments

Comments
 (0)