Add files via upload

If building for iOS, replace the Main branch with these files.

Signed-off-by: rohithzmoi <166651631+rohithzmoi@users.noreply.github.com>
This commit is contained in:
rohithzmoi 2024-09-03 02:15:09 +05:30 committed by GitHub
parent c94fee6743
commit 5b75e89df3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1753 additions and 0 deletions

View File

@ -0,0 +1,177 @@
/*
Copyright (C) 2024 Rohith Namboothiri
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
extern "C" void setupAVAudioSession() {
@try {
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
NSLog(@"Setting up AVAudioSession...");
BOOL success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
withOptions:AVAudioSessionCategoryOptionAllowBluetooth |
AVAudioSessionCategoryOptionMixWithOthers |
AVAudioSessionCategoryOptionDefaultToSpeaker
error:&error];
if (!success || error) {
NSLog(@"Error setting AVAudioSession category: %@, code: %ld", error.localizedDescription, (long)error.code);
return;
}
NSLog(@"AVAudioSession category set to PlayAndRecord with DefaultToSpeaker option");
success = [session setActive:YES error:&error];
if (!success || error) {
NSLog(@"Error activating AVAudioSession: %@, code: %ld", error.localizedDescription, (long)error.code);
return;
}
NSLog(@"AVAudioSession activated successfully");
[[NSNotificationCenter defaultCenter] addObserverForName:AVAudioSessionInterruptionNotification
object:session
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
AVAudioSessionInterruptionType interruptionType = (AVAudioSessionInterruptionType)[note.userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (interruptionType == AVAudioSessionInterruptionTypeBegan) {
NSLog(@"Audio session interruption began");
// Pause audio processing
} else if (interruptionType == AVAudioSessionInterruptionTypeEnded) {
NSLog(@"Audio session interruption ended, attempting to reactivate...");
NSError *activationError = nil;
BOOL reactivationSuccess = [session setActive:YES error:&activationError];
if (!reactivationSuccess) {
NSLog(@"Error re-activating AVAudioSession after interruption: %@, code: %ld", activationError.localizedDescription, (long)activationError.code);
} else {
NSLog(@"Audio session successfully reactivated after interruption");
// Resume audio processing
}
}
}];
[[NSNotificationCenter defaultCenter] addObserverForName:AVAudioSessionRouteChangeNotification
object:session
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
AVAudioSessionRouteChangeReason reason = (AVAudioSessionRouteChangeReason)[note.userInfo[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
NSLog(@"Audio route changed, reason: %lu", (unsigned long)reason);
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable ||
reason == AVAudioSessionRouteChangeReasonNewDeviceAvailable ||
reason == AVAudioSessionRouteChangeReasonOverride) {
NSLog(@"Audio route change detected, attempting to reactivate...");
NSError *activationError = nil;
BOOL reactivationSuccess = [session setActive:YES error:&activationError];
if (!reactivationSuccess) {
NSLog(@"Error re-activating AVAudioSession after route change: %@, code: %ld", activationError.localizedDescription, (long)activationError.code);
} else {
NSLog(@"Audio session successfully reactivated after route change");
}
}
}];
}
@catch (NSException *exception) {
NSLog(@"Exception setting up AVAudioSession: %@", exception.reason);
}
}
extern "C" void deactivateAVAudioSession() {
@try {
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
NSLog(@"Deactivating AVAudioSession...");
BOOL success = [session setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
if (!success || error) {
NSLog(@"Error deactivating AVAudioSession: %@, code: %ld", error.localizedDescription, (long)error.code);
return;
}
NSLog(@"AVAudioSession deactivated successfully");
}
@catch (NSException *exception) {
NSLog(@"Exception deactivating AVAudioSession: %@", exception.reason);
}
}
// Function to handle background audio setup
extern "C" void setupBackgroundAudio() {
@try {
__block UIBackgroundTaskIdentifier bgTask = UIBackgroundTaskInvalid;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"Background task expired. Cleaning up...");
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
NSLog(@"Configuring AVAudioSession for background...");
[session setActive:NO error:nil];
BOOL success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker |
AVAudioSessionCategoryOptionAllowBluetooth |
AVAudioSessionCategoryOptionAllowBluetoothA2DP |
AVAudioSessionCategoryOptionMixWithOthers
error:&error];
if (!success || error) {
NSLog(@"Error setting AVAudioSession category for background: %@, code: %ld", error.localizedDescription, (long)error.code);
} else {
NSLog(@"AVAudioSession category set successfully for background audio");
success = [session setActive:YES error:&error];
if (!success || error) {
NSLog(@"Error activating AVAudioSession in background: %@, code: %ld", error.localizedDescription, (long)error.code);
} else {
NSLog(@"AVAudioSession activated successfully in background");
}
}
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
@catch (NSException *exception) {
NSLog(@"Exception setting up AVAudioSession: %@", exception.reason);
}
}

242
iOS_OnlyFIles/DroidStar.pro Normal file
View File

@ -0,0 +1,242 @@
QT += quick quickcontrols2 network multimedia
//QT += xlsx
unix:!ios:QT += serialport
win32:QT += serialport
!win32:LIBS += -ldl
win32:LIBS += -lws2_32
win32:QMAKE_LFLAGS += -static
QMAKE_LFLAGS_WINDOWS += --enable-stdcall-fixup
RC_ICONS = images/droidstar.ico
ICON = images/droidstar.icns
macx:LIBS += -framework AVFoundation
macx:QMAKE_MACOSX_DEPLOYMENT_TARGET = 12.0
macx:QMAKE_INFO_PLIST = Info.plist.mac
ios:LIBS += -framework AVFoundation -framework AudioToolbox -framework UIKit
ios:QMAKE_IOS_DEPLOYMENT_TARGET=14.0
ios:QMAKE_TARGET_BUNDLE_PREFIX = org.dudetronics
ios:QMAKE_BUNDLE = droidstar
ios:VERSION = 0.43.20
ios:Q_ENABLE_BITCODE.name = ENABLE_BITCODE
ios:Q_ENABLE_BITCODE.value = NO
ios:QMAKE_MAC_XCODE_SETTINGS += Q_ENABLE_BITCODE
ios:QMAKE_ASSET_CATALOGS += Images.xcassets
ios:QMAKE_INFO_PLIST = Info.plist
VERSION_BUILD='$(shell cd $$PWD;git rev-parse --short HEAD)'
DEFINES += VERSION_NUMBER=\"\\\"$${VERSION_BUILD}\\\"\"
DEFINES += QT_DEPRECATED_WARNINGS
#DEFINES += QT_DEBUG_PLUGINS=1
#DEFINES += VOCODER_PLUGIN
#DEFINES += USE_FLITE
#DEFINES += USE_EXTERNAL_CODEC2
#DEFINES += USE_MD380_VOCODER
SOURCES += \
CRCenc.cpp \
vuidupdater.cpp \
LogHandler.cpp \
Golay24128.cpp \
M17Convolution.cpp \
SHA256.cpp \
YSFConvolution.cpp \
YSFFICH.cpp \
audioengine.cpp \
cbptc19696.cpp \
cgolay2087.cpp \
chamming.cpp \
crs129.cpp \
dcs.cpp \
dmr.cpp \
droidstar.cpp \
httpmanager.cpp \
iax.cpp \
imbe_vocoder/aux_sub.cc \
imbe_vocoder/basicop2.cc \
imbe_vocoder/ch_decode.cc \
imbe_vocoder/ch_encode.cc \
imbe_vocoder/dc_rmv.cc \
imbe_vocoder/decode.cc \
imbe_vocoder/dsp_sub.cc \
imbe_vocoder/encode.cc \
imbe_vocoder/imbe_vocoder.cc \
imbe_vocoder/imbe_vocoder_impl.cc \
imbe_vocoder/math_sub.cc \
imbe_vocoder/pe_lpf.cc \
imbe_vocoder/pitch_est.cc \
imbe_vocoder/pitch_ref.cc \
imbe_vocoder/qnt_sub.cc \
imbe_vocoder/rand_gen.cc \
imbe_vocoder/sa_decode.cc \
imbe_vocoder/sa_encode.cc \
imbe_vocoder/sa_enh.cc \
imbe_vocoder/tbls.cc \
imbe_vocoder/uv_synt.cc \
imbe_vocoder/v_synt.cc \
imbe_vocoder/v_uv_det.cc \
m17.cpp \
main.cpp \
mode.cpp \
nxdn.cpp \
p25.cpp \
ref.cpp \
xrf.cpp \
ysf.cpp
# Android-specific source files
android:SOURCES += androidserialport.cpp
# Non-iOS source files
!ios:SOURCES += serialambe.cpp serialmodem.cpp
# Objective-C source files for macOS and iOS
macx:OBJECTIVE_SOURCES += micpermission.mm
ios:OBJECTIVE_SOURCES += micpermission.mm AudioSessionManager.mm
# Enable background audio mode for iOS
ios:QMAKE_MAC_XCODE_SETTINGS += QMAKE_IOS_BACKGROUND_MODES = YES
ios:QMAKE_INFO_PLIST_EXTRA += "<key>UIBackgroundModes</key>"
ios:QMAKE_INFO_PLIST_EXTRA += "<array>"
ios:QMAKE_INFO_PLIST_EXTRA += " <string>audio</string>"
ios:QMAKE_INFO_PLIST_EXTRA += "</array>"
ios:QMAKE_CXXFLAGS += -fobjc-arc
resources.files = main.qml AboutTab.qml HostsTab.qml LogTab.qml MainTab.qml SettingsTab.qml fontawesome-webfont.ttf QsoTab.qml
resources.prefix = /$${TARGET}
RESOURCES += resources
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
CRCenc.h \
DMRDefines.h \
vuidupdater.h \
LogHandler.h \
AudioSessionManager.h \
Golay24128.h \
M17Convolution.h \
M17Defines.h \
MMDVMDefines.h \
SHA256.h \
YSFConvolution.h \
YSFFICH.h \
audioengine.h \
cbptc19696.h \
cgolay2087.h \
chamming.h \
crs129.h \
dcs.h \
dmr.h \
droidstar.h \
httpmanager.h \
iax.h \
iaxdefines.h \
imbe_vocoder/aux_sub.h \
imbe_vocoder/basic_op.h \
imbe_vocoder/ch_decode.h \
imbe_vocoder/ch_encode.h \
imbe_vocoder/dc_rmv.h \
imbe_vocoder/decode.h \
imbe_vocoder/dsp_sub.h \
imbe_vocoder/encode.h \
imbe_vocoder/globals.h \
imbe_vocoder/imbe.h \
imbe_vocoder/imbe_vocoder.h \
imbe_vocoder/imbe_vocoder_api.h \
imbe_vocoder/imbe_vocoder_impl.h \
imbe_vocoder/math_sub.h \
imbe_vocoder/pe_lpf.h \
imbe_vocoder/pitch_est.h \
imbe_vocoder/pitch_ref.h \
imbe_vocoder/qnt_sub.h \
imbe_vocoder/rand_gen.h \
imbe_vocoder/sa_decode.h \
imbe_vocoder/sa_encode.h \
imbe_vocoder/sa_enh.h \
imbe_vocoder/tbls.h \
imbe_vocoder/typedef.h \
imbe_vocoder/typedefs.h \
imbe_vocoder/uv_synt.h \
imbe_vocoder/v_synt.h \
imbe_vocoder/v_uv_det.h \
m17.h \
mode.h \
nxdn.h \
p25.h \
ref.h \
vocoder_plugin.h \
xrf.h \
ysf.h
!contains(DEFINES, USE_EXTERNAL_CODEC2){
HEADERS += \
codec2/codec2_api.h \
codec2/codec2_internal.h \
codec2/defines.h \
codec2/kiss_fft.h \
codec2/lpc.h \
codec2/nlp.h \
codec2/qbase.h \
codec2/quantise.h
SOURCES += \
codec2/codebooks.cpp \
codec2/codec2.cpp \
codec2/kiss_fft.cpp \
codec2/lpc.cpp \
codec2/nlp.cpp \
codec2/pack.cpp \
codec2/qbase.cpp \
codec2/quantise.cpp
}
contains(DEFINES, USE_EXTERNAL_CODEC2){
LIBS += -lcodec2
}
!contains(DEFINES, VOCODER_PLUGIN){
HEADERS += \
mbe/ambe3600x2400_const.h \
mbe/ambe3600x2450_const.h \
mbe/ecc_const.h \
mbe/mbelib.h \
mbe/mbelib_const.h \
mbe/mbelib_parms.h \
mbe/vocoder_plugin.h \
mbe/vocoder_plugin_api.h \
mbe/vocoder_tables.h
SOURCES += \
mbe/ambe3600x2400.c \
mbe/ambe3600x2450.c \
mbe/ecc.c \
mbe/mbelib.c \
mbe/vocoder_plugin.cpp
}
android:HEADERS += androidserialport.h
macx:HEADERS += micpermission.h
!ios:HEADERS += serialambe.h serialmodem.h
android:ANDROID_VERSION_CODE = 79
android:QT_ANDROID_MIN_SDK_VERSION = 31
contains(ANDROID_TARGET_ARCH,armeabi-v7a) {
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
}
contains(ANDROID_TARGET_ARCH,arm64-v8a) {
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
}
contains(DEFINES, USE_FLITE){
LIBS += -lflite_cmu_us_slt -lflite_cmu_us_kal16 -lflite_cmu_us_awb -lflite_cmu_us_rms -lflite_usenglish -lflite_cmulex -lflite -lasound
}
contains(DEFINES, USE_MD380_VOCODER){
LIBS += -lmd380_vocoder -Xlinker --section-start=.firmware=0x0800C000 -Xlinker --section-start=.sram=0x20000000
}

View File

@ -0,0 +1,203 @@
#include "LogHandler.h"
#include <QDir>
#include <QDebug>
#include <QTextStream>
#include <QStandardPaths>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
LogHandler::LogHandler(QObject *parent) : QObject(parent)
{
}
QString LogHandler::getFilePath(const QString &fileName) const
{
QString dirPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(dirPath);
if (!dir.exists()) {
if (!dir.mkpath(dirPath)) {
qDebug() << "Failed to create directory:" << dirPath;
return QString();
}
}
QString filePath = dirPath + "/" + fileName;
qDebug() << "Log file path:" << filePath;
return filePath;
}
bool LogHandler::saveLog(const QString &fileName, const QJsonArray &logData)
{
QString filePath = getFilePath(fileName);
if (filePath.isEmpty()) {
return false;
}
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly)) {
qDebug() << "Failed to open file for writing:" << file.errorString();
return false;
}
QJsonDocument doc(logData);
file.write(doc.toJson());
file.close();
qDebug() << "Log saved successfully.";
return true;
}
QJsonArray LogHandler::loadLog(const QString &fileName)
{
QString filePath = getFilePath(fileName);
QJsonArray logData;
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Failed to open file for reading:" << file.errorString();
return logData;
}
QByteArray data = file.readAll();
QJsonDocument doc(QJsonDocument::fromJson(data));
if (!doc.isNull() && doc.isArray()) {
logData = doc.array();
qDebug() << "Log loaded successfully.";
} else {
qDebug() << "Failed to parse JSON log.";
}
file.close();
return logData;
}
bool LogHandler::clearLog(const QString &fileName)
{
QString filePath = getFilePath(fileName);
QFile file(filePath);
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
file.close();
qDebug() << "Log cleared successfully.";
return true;
}
qDebug() << "Failed to clear log:" << file.errorString();
return false;
}
QString LogHandler::getDSLogPath() const {
QString documentsPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QString dsLogPath = documentsPath + "/DSLog";
QDir dsLogDir(dsLogPath);
qDebug() << "Documents path: " << documentsPath;
qDebug() << "DSLog path: " << dsLogPath;
if (!dsLogDir.exists()) {
if (!dsLogDir.mkpath(dsLogPath)) {
qDebug() << "Failed to create DSLog directory.";
return QString();
} else {
qDebug() << "DSLog directory created successfully.";
}
} else {
qDebug() << "DSLog directory already exists.";
}
return dsLogPath;
}
bool LogHandler::exportLogToCsv(const QString &fileName, const QJsonArray &logData) {
QString dsLogPath = getDSLogPath();
if (dsLogPath.isEmpty()) {
qDebug() << "DSLog path is not available.";
return false;
}
QString filePath = dsLogPath + "/" + QFileInfo(fileName).fileName();
qDebug() << "Attempting to save file at: " << filePath;
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "Failed to open file for writing:" << file.errorString();
return false;
}
QTextStream out(&file);
// Write the CSV headers
out << "Sr.No,Callsign,DMR ID,TGID,Handle,Country,Time\n";
// Write the log data to the CSV file
for (int i = 0; i < logData.size(); ++i) {
QJsonObject entry = logData[i].toObject();
out << entry["serialNumber"].toInt() << ","
<< entry["callsign"].toString() << ","
<< entry["dmrID"].toInt() << ","
<< entry["tgid"].toInt() << ","
<< entry["fname"].toString() << ","
<< entry["country"].toString() << ","
<< entry["currentTime"].toString() << "\n";
}
file.close();
qDebug() << "Log exported successfully to" << filePath;
return true;
}
bool LogHandler::exportLogToAdif(const QString &fileName, const QJsonArray &logData) {
QString dsLogPath = getDSLogPath();
if (dsLogPath.isEmpty()) {
qDebug() << "DSLog path is not available.";
return false;
}
QString filePath = dsLogPath + "/" + QFileInfo(fileName).fileName();
qDebug() << "Attempting to save ADIF file at: " << filePath;
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "Failed to open file for writing:" << file.errorString();
return false;
}
QTextStream out(&file);
// Write the ADIF headers
out << "ADIF Export\n";
out << "<EOH>\n"; // End of Header
// Write each QSO record in ADIF format
for (int i = 0; i < logData.size(); ++i) {
QJsonObject entry = logData[i].toObject();
// Extract and format date and time
QString currentTime = entry["currentTime"].toString();
QString qsoDate = currentTime.left(10).remove('-'); // Format: YYYYMMDD
QString timeOn = currentTime.mid(11, 8).remove(':'); // Format: HHMMSS
// Write each QSO record with valid ADIF tags
out << "<CALL:" << entry["callsign"].toString().length() << ">" << entry["callsign"].toString();
out << "<BAND:4>70CM"; // Band is hardcoded as "70CM"
out << "<MODE:12>DIGITALVOICE"; // Mode is set to "DIGITALVOICE"
// Include the first name in the ADIF record
out << "<NAME:" << entry["fname"].toString().length() << ">" << entry["fname"].toString();
out << "<QSO_DATE:" << qsoDate.length() << ">" << qsoDate;
out << "<TIME_ON:6>" << timeOn;
out << "<EOR>\n"; // End of Record
}
file.close();
qDebug() << "Log exported successfully to" << filePath;
return true;
}
// Extract and display a user-friendly path
QString LogHandler::getFriendlyPath(const QString &fullPath) const {
return fullPath.mid(fullPath.indexOf("/Documents/"));
}

View File

@ -0,0 +1,30 @@
#ifndef LOGHANDLER_H
#define LOGHANDLER_H
#include <QObject>
#include <QJsonArray>
class LogHandler : public QObject
{
Q_OBJECT
public:
explicit LogHandler(QObject *parent = nullptr);
Q_INVOKABLE bool saveLog(const QString &fileName, const QJsonArray &logData);
Q_INVOKABLE QJsonArray loadLog(const QString &fileName);
Q_INVOKABLE bool clearLog(const QString &fileName);
Q_INVOKABLE bool exportLogToCsv(const QString &fileName, const QJsonArray &logData);
Q_INVOKABLE QString getDSLogPath() const; // Updated function name
Q_INVOKABLE QString getFriendlyPath(const QString &path) const; // Mark as Q_INVOKABLE
Q_INVOKABLE bool exportLogToAdif(const QString &fileName, const QJsonArray &logData);
private:
QString getFilePath(const QString &fileName) const;
//QString getDownloadsPath() const;
};
#endif // LOGHANDLER_H

591
iOS_OnlyFIles/QsoTab.qml Normal file
View File

@ -0,0 +1,591 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Dialogs
Item {
id: qsoTab
width: 400
height: 600
property MainTab mainTab: null // This is correctly set from main.qml
property int dmrID: -1
property int tgid: -1
property string logFileName: "logs.json"
property string savedFilePath: ""
//property int latestSerialNumber: 0 // Track the latest serial number
property int latestSerialNumber: 1
// Signals to update MainTab
signal firstRowDataChanged(string serialNumber, string callsign, string handle, string country)
signal secondRowDataChanged(string serialNumber, string callsign, string handle, string country)
ListModel {
id: logModel
}
// Update the first and second row data in a single function
function updateRowData() {
if (logModel.count > 0) {
var firstRow = logModel.get(0);
firstRowDataChanged(firstRow.serialNumber, firstRow.callsign, firstRow.fname, firstRow.country);
} else {
firstRowDataChanged("0", "N/A", "N/A", "N/A");
}
if (logModel.count > 1) {
var secondRow = logModel.get(1);
secondRowDataChanged(secondRow.serialNumber, secondRow.callsign, secondRow.fname, secondRow.country);
} else {
secondRowDataChanged("0", "N/A", "N/A", "N/A");
}
}
// Connections to update row data whenever the model changes
Connections {
target: logModel
onCountChanged: {
updateRowData();
saveSettings(); // Save logs when the model changes
}
}
// Component onCompleted: Initial setup
Component.onCompleted: {
mainTab.dataUpdated.connect(onDataUpdated);
updateRowData(); // Ensure both rows are updated initially
loadSettings();
if (mainTab === null) {
console.error("mainTab is null. Ensure it is passed correctly from the parent.");
}
}
function saveSettings() {
var logData = [];
for (var i = 0; i < logModel.count; i++) {
logData.push(logModel.get(i));
}
logHandler.saveLog(logFileName, logData);
}
function loadSettings() {
var savedData = logHandler.loadLog(logFileName);
for (var i = 0; i < savedData.length; i++) {
savedData[i].checked = false;
logModel.append(savedData[i]);
latestSerialNumber = Math.max(latestSerialNumber, savedData[i].serialNumber + 1);
}
}
function clearSettings() {
logModel.clear();
logHandler.clearLog(logFileName);
latestSerialNumber = 0; // Reset the serial number counter to 0
}
function exportLog() {
saveFileNameDialog.open(); // Prompt for file name
}
// Header Row with Text and Clear Button
Text {
id: headerText
text: "This page logs lastheard stations in descending order. An upgrade to a native Log book is coming soon."
wrapMode: Text.WordWrap
font.bold: true
font.pointSize: 12
color: "white"
width: parent.width - 50
x: 20
y: 10
}
Button {
id: clearButton
text: "Clear"
x: 20
y: headerText.y + headerText.height + 12
onClicked: {
clearSettings();
}
background: Rectangle {
color: "red" // Set the background color to red
radius: 4 // Optional: Add some rounding to the corners
}
contentItem: Text {
text: clearButton.text
color: "white" // Set text color to white for contrast
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.centerIn: parent
font.bold: true
}
}
// Add an Export button
Button {
id: exportButton
text: "Export Log"
x: clearButton.x + clearButton.width + 10
y: headerText.y + headerText.height + 12
onClicked: exportLog()
background: Rectangle {
color: "green" // Set the background color to green
radius: 4 // Optional: Add some rounding to the corners
}
contentItem: Text {
text: exportButton.text
color: "white" // Set text color to white for contrast
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.centerIn: parent
font.bold: true
}
}
// Clone the button using properties from mainTab's buttonTX
Button {
id: clonedButton
visible: mainTab.buttonTX.visible
enabled: mainTab.buttonTX.enabled
x: exportButton.x + exportButton.width + 10
y: exportButton.y
width: exportButton.width * 1.5 // Increase width to accommodate more text
height: exportButton.height
background: Rectangle {
color: mainTab.buttonTX.tx ? "#800000" : "steelblue"
radius: 4
Column {
anchors.centerIn: parent
spacing: 2 // Add some spacing between the texts
Text {
id: clonedText
font.pointSize: 20 // Adjust font size as needed
text: qsTr("TX") // Display "TX"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
// Ensure cloned button follows the same behavior as the original
onClicked: mainTab.buttonTX.clicked()
onPressed: mainTab.buttonTX.pressed()
onReleased: mainTab.buttonTX.released()
onCanceled: mainTab.buttonTX.canceled()
}
// Dialog to prompt for file name
Dialog {
id: saveFileNameDialog
title: "Enter File Name and Choose Format"
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
contentItem: Column {
spacing: 10
TextField {
id: fileNameInput
placeholderText: "Enter file name"
width: parent.width - 20
}
Row {
spacing: 10
RadioButton {
id: csvRadioButton
text: "CSV"
checked: true // Default to CSV
}
RadioButton {
id: adifRadioButton
text: "ADIF"
}
}
}
onAccepted: {
var logData = [];
var hasSelection = false;
for (var i = 0; i < logModel.count; i++) {
var entry = logModel.get(i);
if (entry.checked) {
logData.push(entry);
hasSelection = true;
}
}
if (!hasSelection) { // If no selections, export all
for (var i = 0; i < logModel.count; i++) {
logData.push(logModel.get(i));
}
}
var fileName = fileNameInput.text;
var filePath = logHandler.getDSLogPath() + "/" + fileName;
if (csvRadioButton.checked) {
fileName += ".csv";
filePath += ".csv";
if (logHandler.exportLogToCsv(filePath, logData)) {
savedFilePath = logHandler.getFriendlyPath(filePath);
fileSavedDialog.open();
} else {
console.error("Failed to save the CSV file.");
}
} else if (adifRadioButton.checked) {
fileName += ".adi";
filePath += ".adi";
if (logHandler.exportLogToAdif(filePath, logData)) {
savedFilePath = logHandler.getFriendlyPath(filePath);
fileSavedDialog.open();
} else {
console.error("Failed to save the ADIF file.");
}
}
}
}
// Dialog to show that the file was saved
Dialog {
id: fileSavedDialog
title: "File Saved"
standardButtons: Dialog.Ok
width: 300 // Set a fixed width for the dialog to avoid binding loops
onAccepted: {
console.log("File saved successfully!");
}
background: Rectangle {
color: "#80c342"
radius: 8 // Optional: Add rounded corners
}
contentItem: Text {
text: "File saved successfully to " + savedFilePath
font.pointSize: 14
color: "black"
wrapMode: Text.WordWrap // Enable text wrapping
width: parent.width * 0.9 // Ensure some padding from the edges
}
}
Row {
id: tableHeader
width: parent.width
height: 25 // Make the rectangles slightly smaller in height
y: clearButton.y + clearButton.height + 10
spacing: 4 // Reduce spacing slightly
Rectangle {
width: parent.width / 8
height: parent.height
color: "darkgrey"
Text {
anchors.centerIn: parent
text: "Sr.No"
font.bold: true
font.pixelSize: 12 // Smaller font size
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "darkgrey"
Text {
anchors.centerIn: parent
text: "Callsign"
font.bold: true
font.pixelSize: 12 // Smaller font size
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "darkgrey"
Text {
anchors.centerIn: parent
text: "DMR ID"
font.bold: true
font.pixelSize: 12 // Smaller font size
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "darkgrey"
Text {
anchors.centerIn: parent
text: "TGID"
font.bold: true
font.pixelSize: 12 // Smaller font size
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "darkgrey"
Text {
anchors.centerIn: parent
text: "Handle"
font.bold: true
font.pixelSize: 12 // Smaller font size
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "darkgrey"
Text {
anchors.centerIn: parent
text: "Country"
font.bold: true
font.pixelSize: 12 // Smaller font size
}
}
}
// The TableView for the rows
TableView {
id: tableView
x: 0
y: tableHeader.y + tableHeader.height + 10
width: parent.width
height: parent.height - (tableHeader.y + tableHeader.height + 30)
model: logModel
delegate: Rectangle {
width: tableView.width
implicitWidth: tableView.width
implicitHeight: 100 // Adjust the height to accommodate the checkbox and time text
height: implicitHeight
color: checkBox.checked ? "#b9fbd7" : (index % 2 === 0 ? "lightgrey" : "white") // Changes color when checked
Row {
width: parent.width
height: 40 // Set a fixed height for the row
Rectangle {
width: parent.width / 8
height: parent.height
color: "transparent"
Text {
anchors.centerIn: parent
text: serialNumber // Using direct access to model properties
font.pixelSize: 12
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "transparent"
Text {
anchors.centerIn: parent
text: callsign // Using direct access to model properties
font.pixelSize: 12
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "transparent"
Text {
anchors.centerIn: parent
text: dmrID // Using direct access to model properties
font.pixelSize: 12
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "transparent"
Text {
anchors.centerIn: parent
text: tgid // Using direct access to model properties
font.pixelSize: 12
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "transparent"
Text {
anchors.centerIn: parent
text: fname // Using direct access to model properties
font.pixelSize: 12
}
}
Rectangle {
width: parent.width / 6
height: parent.height
color: "transparent"
Text {
anchors.centerIn: parent
text: country // Using direct access to model properties
font.pixelSize: 12
}
}
}
Row {
width: parent.width - 20
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
y: 40
CheckBox {
id: checkBox
checked: model.checked !== undefined ? model.checked : false
onCheckedChanged: {
model.checked = checked;
}
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: model.currentTime
wrapMode: Text.WordWrap
font.pixelSize: 12
width: parent.width - checkBox.width - 50
anchors.verticalCenter: checkBox.verticalCenter
elide: Text.ElideRight
}
Text {
id: menuIcon
text: "\uf0c9" // FontAwesome icon for bars (hamburger menu)
font.family: "FontAwesome"
font.pixelSize: 20
anchors.verticalCenter: parent.verticalCenter
color: "black"
MouseArea {
anchors.fill: parent
onClicked: {
if (contextMenu.visible) {
contextMenu.close();
} else {
contextMenu.x = menuIcon.x + menuIcon.width
contextMenu.y = menuIcon.y
contextMenu.open()
}
}
}
}
}
Menu {
id: contextMenu
title: "Lookup Options"
visible: false // Ensure it's not visible by default
MenuItem {
text: "Lookup QRZ"
onTriggered: Qt.openUrlExternally("https://qrz.com/lookup/" + model.callsign)
}
MenuItem {
text: "Lookup BM"
onTriggered: Qt.openUrlExternally("https://brandmeister.network/index.php?page=profile&call=" + model.callsign)
}
MenuItem {
text: "Lookup APRS"
onTriggered: Qt.openUrlExternally("https://aprs.fi/#!call=a%2F" + model.callsign)
}
// onClosed: visible = false
}
}
}
function onDataUpdated(receivedDmrID, receivedTGID) {
console.log("Received dmrID:", receivedDmrID, "and TGID:", receivedTGID);
qsoTab.dmrID = receivedDmrID; // Correctly scope the dmrID assignment
qsoTab.tgid = receivedTGID; // Set the TGID in qsoTab
fetchData(receivedDmrID, receivedTGID);
}
function fetchData(dmrID, tgid) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://radioid.net/api/dmr/user/?id=" + dmrID, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
if (response.count > 0) {
var result = response.results[0];
var data = {
callsign: result.callsign,
dmrID: result.id,
tgid: tgid, // Include TGID in the data object
country: result.country,
fname: result.fname,
currentTime: Qt.formatDateTime(new Date(), "yyyy-MM-dd HH:mm:ss") // Current local time
};
addEntry(data);
}
} else {
console.error("Failed to fetch data. Status:", xhr.status);
}
}
};
xhr.send();
}
function addEntry(data) {
console.log("Processing data in QsoTab:", JSON.stringify(data));
if (!data || typeof data !== 'object') {
console.error("Invalid data received:", data);
return;
}
// Increment the latest serial number
latestSerialNumber += 1;
// Check and update the country field
if (data.country === "United States") {
data.country = "USA";
} else if (data.country === "United Kingdom") {
data.country = "UK";
}
// Insert the new entry with the next serial number
logModel.insert(0, {
serialNumber: latestSerialNumber,
callsign: data.callsign,
dmrID: data.dmrID,
tgid: data.tgid,
country: data.country,
fname: data.fname,
currentTime: data.currentTime,
checked: false
});
//latestSerialNumber += 1; // Increment for the next entry
// Save the updated log
saveSettings();
// Ensure that the log doesn't exceed the maximum number of entries
const maxEntries = 250;
while (logModel.count > maxEntries) {
logModel.remove(logModel.count - 1); // Remove the oldest entry
}
}
}

View File

@ -0,0 +1,401 @@
/*
Copyright (C) 2019-2021 Doug McLain
Modified Copyright (C) 2024 Rohith Namboothiri
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "audioengine.h"
#include <QDebug>
#include <cmath>
#include "AudioSessionManager.h"
#if defined (Q_OS_MACOS) || defined(Q_OS_IOS)
#define MACHAK 1
#else
#define MACHAK 0
#endif
AudioEngine::AudioEngine(QString in, QString out) :
m_outputdevice(out),
m_inputdevice(in),
m_out(nullptr),
m_in(nullptr),
m_srm(1),
m_dataWritten(false)
{
m_audio_out_temp_buf_p = m_audio_out_temp_buf;
memset(m_aout_max_buf, 0, sizeof(float) * 200);
m_aout_max_buf_p = m_aout_max_buf;
m_aout_max_buf_idx = 0;
m_aout_gain = 100;
m_volume = 1.0f;
}
AudioEngine::~AudioEngine()
{
}
QStringList AudioEngine::discover_audio_devices(uint8_t d)
{
QStringList list;
QList<QAudioDevice> devices;
if(d){
devices = QMediaDevices::audioOutputs();
}
else{
devices = QMediaDevices::audioInputs();
}
for (QList<QAudioDevice>::ConstIterator it = devices.constBegin(); it != devices.constEnd(); ++it ) {
//fprintf(stderr, "Playback device name = %s\n", (*it).deviceName().toStdString().c_str());fflush(stderr);
list.append((*it).description());
}
return list;
}
void AudioEngine::init()
{
QAudioFormat format;
format.setSampleRate(8000);
format.setChannelCount(1);
format.setSampleFormat(QAudioFormat::Int16);
m_agc = true;
QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
if(devices.size() == 0){
qDebug() << "No audio playback hardware found";
}
else{
QAudioDevice device(QMediaDevices::defaultAudioOutput());
for (QList<QAudioDevice>::ConstIterator it = devices.constBegin(); it != devices.constEnd(); ++it ) {
qDebug() << "Playback device name = " << (*it).description();
qDebug() << (*it).supportedSampleFormats();
qDebug() << (*it).preferredFormat();
if((*it).description() == m_outputdevice){
device = *it;
}
}
if (!device.isFormatSupported(format)) {
qWarning() << "Raw audio format not supported by playback device";
}
qDebug() << "Playback device: " << device.description() << "SR: " << format.sampleRate();
m_out = new QAudioSink(device, format, this);
m_out->setBufferSize(1280);
connect(m_out, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State)));
}
devices = QMediaDevices::audioInputs();
if(devices.size() == 0){
qDebug() << "No audio capture hardware found";
}
else{
QAudioDevice device(QMediaDevices::defaultAudioInput());
for (QList<QAudioDevice>::ConstIterator it = devices.constBegin(); it != devices.constEnd(); ++it ) {
if(MACHAK){
qDebug() << "Playback device name = " << (*it).description();
qDebug() << (*it).supportedSampleFormats();
qDebug() << (*it).preferredFormat();
}
if((*it).description() == m_inputdevice){
device = *it;
}
}
if (!device.isFormatSupported(format)) {
qWarning() << "Raw audio format not supported by capture device";
}
int sr = 8000;
if(MACHAK){
sr = device.preferredFormat().sampleRate();
m_srm = (float)sr / 8000.0;
}
format.setSampleRate(sr);
m_in = new QAudioSource(device, format, this);
qDebug() << "Capture device: " << device.description() << " SR: " << sr << " resample factor: " << m_srm;
}
}
void AudioEngine::start_capture()
{
m_audioinq.clear();
// setupAVAudioSession();
if(m_in != nullptr){
m_indev = m_in->start();
if(MACHAK) m_srm = (float)(m_in->format().sampleRate()) / 8000.0;
connect(m_indev, SIGNAL(readyRead()), SLOT(input_data_received()));
}
}
void AudioEngine::stop_capture()
{
if(m_in != nullptr){
m_indev->disconnect();
m_in->stop();
}
}
void AudioEngine::start_playback()
{
m_outdev = m_out->start();
setupAVAudioSession();
setupBackgroundAudio();
}
void AudioEngine::stop_playback()
{
//m_outdev->reset();
m_out->reset();
m_out->stop();
static bool isSessionActive = false;
if (isSessionActive) {
deactivateAVAudioSession();
isSessionActive = false; // Reset session state before reactivation
}
}
void AudioEngine::input_data_received()
{
QByteArray data = m_indev->readAll();
if (data.size() > 0){
/*
fprintf(stderr, "AUDIOIN: ");
for(int i = 0; i < len; ++i){
fprintf(stderr, "%02x ", (uint8_t)data.data()[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
*/
if(MACHAK){
std::vector<int16_t> samples;
for(int i = 0; i < data.size(); i += 2){
samples.push_back(((data.data()[i+1] << 8) & 0xff00) | (data.data()[i] & 0xff));
}
for(float i = 0; i < (float)data.size()/2; i += m_srm){
m_audioinq.enqueue(samples[i]);
}
}
else{
for(int i = 0; i < data.size(); i += (2 * m_srm)){
m_audioinq.enqueue(((data.data()[i+1] << 8) & 0xff00) | (data.data()[i] & 0xff));
}
}
}
}
void AudioEngine::write(int16_t *pcm, size_t s)
{
m_maxlevel = 0;
/*
fprintf(stderr, "AUDIOOUT: ");
for(int i = 0; i < s; ++i){
fprintf(stderr, "%04x ", (uint16_t)pcm[i]);
}
fprintf(stderr, "\n");
fflush(stderr);
*/
if(m_agc){
process_audio(pcm, s);
}
size_t l = m_outdev->write((const char *) pcm, sizeof(int16_t) * s);
if (l > 0) { // Data was written
m_dataWritten = true;
}
if (l*2 < s){
qDebug() << "AudioEngine::write() " << s << ":" << l << ":" << (int)m_out->bytesFree() << ":" << m_out->bufferSize() << ":" << m_out->error();
}
for(uint32_t i = 0; i < s; ++i){
if(pcm[i] > m_maxlevel){
m_maxlevel = pcm[i];
}
}
}
uint16_t AudioEngine::read(int16_t *pcm, int s)
{
m_maxlevel = 0;
if(m_audioinq.size() >= s){
for(int i = 0; i < s; ++i){
pcm[i] = m_audioinq.dequeue();
if(pcm[i] > m_maxlevel){
m_maxlevel = pcm[i];
}
}
return 1;
}
else if(m_in == nullptr){
memset(pcm, 0, sizeof(int16_t) * s);
return 1;
}
else{
return 0;
}
}
uint16_t AudioEngine::read(int16_t *pcm)
{
int s;
m_maxlevel = 0;
if(m_audioinq.size() >= 160){
s = 160;
}
else{
s = m_audioinq.size();
}
for(int i = 0; i < s; ++i){
pcm[i] = m_audioinq.dequeue();
if(pcm[i] > m_maxlevel){
m_maxlevel = pcm[i];
}
}
return s;
}
// process_audio() based on code from DSD https://github.com/szechyjs/dsd
void AudioEngine::process_audio(int16_t *pcm, size_t s)
{
float aout_abs, max, gainfactor, gaindelta, maxbuf;
for(size_t i = 0; i < s; ++i){
m_audio_out_temp_buf[i] = static_cast<float>(pcm[i]);
}
// detect max level
max = 0;
m_audio_out_temp_buf_p = m_audio_out_temp_buf;
for (size_t i = 0; i < s; i++){
aout_abs = fabsf(*m_audio_out_temp_buf_p);
if (aout_abs > max){
max = aout_abs;
}
m_audio_out_temp_buf_p++;
}
*m_aout_max_buf_p = max;
m_aout_max_buf_p++;
m_aout_max_buf_idx++;
if (m_aout_max_buf_idx > 24){
m_aout_max_buf_idx = 0;
m_aout_max_buf_p = m_aout_max_buf;
}
// lookup max history
for (size_t i = 0; i < 25; i++){
maxbuf = m_aout_max_buf[i];
if (maxbuf > max){
max = maxbuf;
}
}
// determine optimal gain level
if (max > static_cast<float>(0)){
gainfactor = (static_cast<float>(30000) / max);
}
else{
gainfactor = static_cast<float>(50);
}
if (gainfactor < m_aout_gain){
m_aout_gain = gainfactor;
gaindelta = static_cast<float>(0);
}
else{
if (gainfactor > static_cast<float>(50)){
gainfactor = static_cast<float>(50);
}
gaindelta = gainfactor - m_aout_gain;
if (gaindelta > (static_cast<float>(0.05) * m_aout_gain)){
gaindelta = (static_cast<float>(0.05) * m_aout_gain);
}
}
gaindelta /= static_cast<float>(s); //160
// adjust output gain
m_audio_out_temp_buf_p = m_audio_out_temp_buf;
for (size_t i = 0; i < s; i++){
*m_audio_out_temp_buf_p = (m_aout_gain + (static_cast<float>(i) * gaindelta)) * (*m_audio_out_temp_buf_p);
m_audio_out_temp_buf_p++;
}
m_aout_gain += (static_cast<float>(s) * gaindelta);
m_audio_out_temp_buf_p = m_audio_out_temp_buf;
for (size_t i = 0; i < s; i++){
*m_audio_out_temp_buf_p *= m_volume;
if (*m_audio_out_temp_buf_p > static_cast<float>(32760)){
*m_audio_out_temp_buf_p = static_cast<float>(32760);
}
else if (*m_audio_out_temp_buf_p < static_cast<float>(-32760)){
*m_audio_out_temp_buf_p = static_cast<float>(-32760);
}
pcm[i] = static_cast<int16_t>(*m_audio_out_temp_buf_p);
m_audio_out_temp_buf_p++;
}
}
void AudioEngine::handleStateChanged(QAudio::State newState)
{
switch (newState) {
case QAudio::ActiveState:
qDebug() << "AudioOut state active";
setupAVAudioSession();
break;
case QAudio::SuspendedState:
qDebug() << "AudioOut state suspended";
//setupBackgroundAudio();
break;
case QAudio::IdleState:
qDebug() << "AudioOut state idle";
setupBackgroundAudio();
break;
case QAudio::StoppedState:
qDebug() << "AudioOut state stopped";
break;
default:
break;
}
}

109
iOS_OnlyFIles/audioengine.h Normal file
View File

@ -0,0 +1,109 @@
/*
Copyright (C) 2019-2021 Doug McLain
Modified Copyright (C) 2024 Rohith Namboothiri
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef AUDIOENGINE_H
#define AUDIOENGINE_H
#include <QObject>
#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0)
#include <QAudio>
#include <QAudioFormat>
#include <QAudioInput>
#else
#include <QAudioDevice>
#include <QAudioSink>
#include <QAudioSource>
#include <QMediaDevices>
#endif
#include <QAudioOutput>
#include <QQueue>
#define AUDIO_OUT 1
#define AUDIO_IN 0
class AudioEngine : public QObject
{
Q_OBJECT
public:
//explicit AudioEngine(QObject *parent = nullptr);
AudioEngine(QString in, QString out);
~AudioEngine();
static QStringList discover_audio_devices(uint8_t d);
void init();
void start_capture();
void stop_capture();
void start_playback();
void stop_playback();
void write(int16_t *, size_t);
void set_output_buffer_size(uint32_t b) { m_out->setBufferSize(b); }
void set_input_buffer_size(uint32_t b) { if(m_in != nullptr) m_in->setBufferSize(b); }
void set_output_volume(qreal v){ m_out->setVolume(v); }
void set_input_volume(qreal v){ if(m_in != nullptr) m_in->setVolume(v); }
void set_agc(bool agc) { m_agc = agc; }
bool frame_available() { return (m_audioinq.size() >= 320) ? true : false; }
uint16_t read(int16_t *, int);
uint16_t read(int16_t *);
uint16_t level() { return m_maxlevel; }
bool shouldResumePlayback();
bool shouldRestartPlayback();
bool m_dataWritten;
signals:
private:
QString m_outputdevice;
QString m_inputdevice;
#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0)
QAudioOutput *m_out;
QAudioInput *m_in;
#else
QAudioSink *m_out;
QAudioSource *m_in;
#endif
QIODevice *m_outdev;
QIODevice *m_indev;
QQueue<int16_t> m_audioinq;
uint16_t m_maxlevel;
bool m_agc;
float m_srm; // sample rate multiplier for macOS HACK
float m_audio_out_temp_buf[320]; //!< output of decoder
float *m_audio_out_temp_buf_p;
//float m_audio_out_float_buf[1120]; //!< output of upsampler - 1 frame of 160 samples upampled up to 7 times
//float *m_audio_out_float_buf_p;
float m_aout_max_buf[200];
float *m_aout_max_buf_p;
int m_aout_max_buf_idx;
//short m_audio_out_buf[2*48000]; //!< final result - 1s of L+R S16LE samples
//short *m_audio_out_buf_p;
//int m_audio_out_nb_samples;
//int m_audio_out_buf_size;
//int m_audio_out_idx;
//int m_audio_out_idx2;
float m_aout_gain;
float m_volume;
private slots:
void input_data_received();
void process_audio(int16_t *pcm, size_t s);
void handleStateChanged(QAudio::State newState);
};
#endif // AUDIOENGINE_H