Browse Source

first commit

master
SO_RA 1 year ago
commit
5e720d2995
Signed by: Mashiro_Sorata
GPG Key ID: 2C0B3C32FD531F77
  1. BIN
      Locales/ja_JP.qm
  2. BIN
      Locales/zh.qm
  3. BIN
      Locales/zh_CN.qm
  4. 4
      Presets/Circle/preset.json
  5. BIN
      Presets/Circle/preview.png
  6. 8
      Presets/Circle/settings.xml
  7. 4
      Presets/Line/preset.json
  8. BIN
      Presets/Line/preview.png
  9. 6
      Presets/Line/settings.xml
  10. 4
      Presets/Ordinal_Scale_UI_bottom/preset.json
  11. BIN
      Presets/Ordinal_Scale_UI_bottom/preview.png
  12. 10
      Presets/Ordinal_Scale_UI_bottom/settings.xml
  13. 4
      Presets/Solidcircle/preset.json
  14. BIN
      Presets/Solidcircle/preview.png
  15. 9
      Presets/Solidcircle/settings.xml
  16. 4
      Presets/Waves/preset.json
  17. BIN
      Presets/Waves/preview.png
  18. 7
      Presets/Waves/settings.xml
  19. BIN
      bin/ADVServer.exe
  20. 13
      bin/advConfig.ini
  21. 23
      module.qml
  22. 144
      package.json
  23. BIN
      preview.png
  24. 133
      qml/Common.qml
  25. 60
      qml/Main.qml
  26. 181
      qml/StylePreferences.qml
  27. 25
      qml/WSocket.qml
  28. 41
      qml/api/CfgAPI.qml
  29. 33
      qml/api/StyleAPI.qml
  30. 1
      qml/qmldir
  31. 65
      readme.md
  32. 111
      styles/Preset_Ordinal_Scale_UI_bottom/Config.qml
  33. 123
      styles/Preset_Ordinal_Scale_UI_bottom/Style.qml
  34. 139
      styles/Preset_circle/Config.qml
  35. 132
      styles/Preset_circle/Style.qml
  36. 150
      styles/Preset_line/Config.qml
  37. 163
      styles/Preset_line/Style.qml
  38. 139
      styles/Preset_solidcircle/Config.qml
  39. 149
      styles/Preset_solidcircle/Style.qml
  40. 53
      styles/Preset_waves/Config.qml
  41. 62
      styles/Preset_waves/Style.qml

BIN
Locales/ja_JP.qm

Binary file not shown.

BIN
Locales/zh.qm

Binary file not shown.

BIN
Locales/zh_CN.qm

Binary file not shown.

4
Presets/Circle/preset.json

@ -0,0 +1,4 @@
{
"source": "nvg://advp.widget.mashiros.top/widget",
"settings": "settings.xml"
}

BIN
Presets/Circle/preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

8
Presets/Circle/settings.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<map>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset_line#Style.qml">{"__version":"1.0.0","__cfg_height":580,"Center Line":true,"Center Color":"#ff4500","Line Color":"#ff4500","Line Position":0,"Data Length":0,"Channel":2,"Reverse":false,"Rotate Settings":{"Center Enable":false,"Center Angle":10,"Line Enable":false,"Line Angle":10},"Data Settings":{"Auto Normalizing":true,"Amplitude":4,"Unit Style":0}}</value>
<value name="styles">{"index":2}</value>
<value name="current_style">"nvg://advp.widget.mashiros.top/advp-style-preset-circle#Style.qml"</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset_waves#Style.qml">{"__version":"1.0.0","__cfg_height":580,"Line Width":1,"Line Color":"#ff4500","Data Settings":{"Auto Normalizing":true,"Amplitude":10}}</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset-circle#Style.qml">{"__version":"1.0.0","__cfg_height":740,"Main Color":"#ff4500","Line Position":0,"Line Width":1,"Max Range":50,"Data Length":0,"Channel":2,"Reverse":false,"Rotate":false,"Ratate Speed":10,"Angle":0,"Data Settings":{"Auto Normalizing":true,"Amplitude":10,"Unit Style":0}}</value>
</map>

4
Presets/Line/preset.json

@ -0,0 +1,4 @@
{
"source": "nvg://advp.widget.mashiros.top/widget",
"settings": "settings.xml"
}

BIN
Presets/Line/preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

6
Presets/Line/settings.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<map>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset_line#Style.qml">{"__version":"1.0.0","__cfg_height":580,"Center Line":true,"Center Color":"#ff4500","Line Color":"#ff4500","Line Position":0,"Data Length":0,"Channel":2,"Reverse":false,"Rotate Settings":{"Center Enable":false,"Center Angle":10,"Line Enable":false,"Line Angle":10},"Data Settings":{"Auto Normalizing":true,"Amplitude":4,"Unit Style":0}}</value>
<value name="styles">{"index":0}</value>
<value name="current_style">"nvg://advp.widget.mashiros.top/advp-style-preset_line#Style.qml"</value>
</map>

4
Presets/Ordinal_Scale_UI_bottom/preset.json

@ -0,0 +1,4 @@
{
"source": "nvg://advp.widget.mashiros.top/widget",
"settings": "settings.xml"
}

BIN
Presets/Ordinal_Scale_UI_bottom/preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

10
Presets/Ordinal_Scale_UI_bottom/settings.xml

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<map>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset_line#Style.qml">{"__version":"1.0.0","__cfg_height":580,"Center Line":true,"Center Color":"#ff4500","Line Color":"#ff4500","Line Position":0,"Data Length":0,"Channel":2,"Reverse":false,"Rotate Settings":{"Center Enable":false,"Center Angle":10,"Line Enable":false,"Line Angle":10},"Data Settings":{"Auto Normalizing":true,"Amplitude":4,"Unit Style":0}}</value>
<value name="styles">{"index":4}</value>
<value name="current_style">"nvg://advp.widget.mashiros.top/advp-style-preset-ordinal_scale_ui_bottom#Style.qml"</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset_waves#Style.qml">{"__version":"1.0.0","__cfg_height":580,"Line Width":1,"Line Color":"#ff4500","Data Settings":{"Auto Normalizing":true,"Amplitude":10}}</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset-circle#Style.qml">{"__version":"1.0.0","__cfg_height":740,"Main Color":"#ff4500","Line Position":0,"Line Width":1,"Max Range":50,"Data Length":0,"Channel":2,"Reverse":false,"Rotate":false,"Ratate Speed":10,"Angle":0,"Data Settings":{"Auto Normalizing":true,"Amplitude":10,"Unit Style":0}}</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset-solidcircle#Style.qml">{"__version":"1.0.0","__cfg_height":740,"Main Color":"#ff4500","Line Position":0,"Line Width":1,"Max Range":50,"Data Length":0,"Channel":2,"Reverse":false,"Rotate":false,"Ratate Speed":10,"Angle":0,"Data Settings":{"Auto Normalizing":true,"Amplitude":10,"Unit Style":0}}</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset-ordinal_scale_ui_bottom#Style.qml">{"__version":"1.0.0","__cfg_height":710,"Bass Color":"#dc143c","Alto Color":"#f8f8ff","Treble Color":"#4169e1","Bass AM":100,"Alto AM":150,"Treble AM":200,"Static AM":15,"Speed":20,"Data Settings":{"Auto Normalizing":true,"Amplitude":10}}</value>
</map>

4
Presets/Solidcircle/preset.json

@ -0,0 +1,4 @@
{
"source": "nvg://advp.widget.mashiros.top/widget",
"settings": "settings.xml"
}

BIN
Presets/Solidcircle/preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

9
Presets/Solidcircle/settings.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<map>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset_line#Style.qml">{"__version":"1.0.0","__cfg_height":580,"Center Line":true,"Center Color":"#ff4500","Line Color":"#ff4500","Line Position":0,"Data Length":0,"Channel":2,"Reverse":false,"Rotate Settings":{"Center Enable":false,"Center Angle":10,"Line Enable":false,"Line Angle":10},"Data Settings":{"Auto Normalizing":true,"Amplitude":4,"Unit Style":0}}</value>
<value name="styles">{"index":3}</value>
<value name="current_style">"nvg://advp.widget.mashiros.top/advp-style-preset-solidcircle#Style.qml"</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset_waves#Style.qml">{"__version":"1.0.0","__cfg_height":580,"Line Width":1,"Line Color":"#ff4500","Data Settings":{"Auto Normalizing":true,"Amplitude":10}}</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset-circle#Style.qml">{"__version":"1.0.0","__cfg_height":740,"Main Color":"#ff4500","Line Position":0,"Line Width":1,"Max Range":50,"Data Length":0,"Channel":2,"Reverse":false,"Rotate":false,"Ratate Speed":10,"Angle":0,"Data Settings":{"Auto Normalizing":true,"Amplitude":10,"Unit Style":0}}</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset-solidcircle#Style.qml">{"__version":"1.0.0","__cfg_height":740,"Main Color":"#ff4500","Line Position":0,"Line Width":1,"Max Range":50,"Data Length":0,"Channel":2,"Reverse":false,"Rotate":false,"Ratate Speed":10,"Angle":0,"Data Settings":{"Auto Normalizing":true,"Amplitude":10,"Unit Style":0}}</value>
</map>

4
Presets/Waves/preset.json

@ -0,0 +1,4 @@
{
"source": "nvg://advp.widget.mashiros.top/widget",
"settings": "settings.xml"
}

BIN
Presets/Waves/preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

7
Presets/Waves/settings.xml

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<map>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset_line#Style.qml">{"__version":"1.0.0","__cfg_height":580,"Center Line":true,"Center Color":"#ff4500","Line Color":"#ff4500","Line Position":0,"Data Length":0,"Channel":2,"Reverse":false,"Rotate Settings":{"Center Enable":false,"Center Angle":10,"Line Enable":false,"Line Angle":10},"Data Settings":{"Auto Normalizing":true,"Amplitude":4,"Unit Style":0}}</value>
<value name="styles">{"index":1}</value>
<value name="current_style">"nvg://advp.widget.mashiros.top/advp-style-preset_waves#Style.qml"</value>
<value name="nvg://advp.widget.mashiros.top/advp-style-preset_waves#Style.qml">{"__version":"1.0.0","__cfg_height":580,"Line Width":1,"Line Color":"#ff4500","Data Settings":{"Auto Normalizing":true,"Amplitude":10}}</value>
</map>

BIN
bin/ADVServer.exe

Binary file not shown.

13
bin/advConfig.ini

@ -0,0 +1,13 @@
[Server]
ip = local
port = 5055
maxClient = 10
logger = true
[FFT]
attack = 5
decay = 5
norspeed = 1
peakthr = 10
fps = 35
changeSpeed = 20

23
module.qml

@ -0,0 +1,23 @@
import NERvGear 1.0 as NVG
import NERvGear.Private 1.0 as NVGP
import "./qml"
NVG.Module {
initialize: function () {
console.log("Initializing ADV-Plugin.");
Common.execute("../bin/ADVServer.exe", "-reboot");
Common.setWsocket(true);
return true;
}
ready: function () {
console.log("ADV-Plugin is ready.");
}
cleanup: function () {
console.log("Cleaning up ADV-Plugin.");
Common.setWsocket(false);
Common.execute("../bin/ADVServer.exe", "-close");
}
}

144
package.json

@ -0,0 +1,144 @@
{
"name": "top.mashiros.widget.advp",
"version": "1.0.0",
"title": {
"en": "ADV Plugin",
"zh": "音頻可視化插件",
"zh_CN": "音频可视化插件",
"ja_JP": "音声視覚化プラグイン"
},
"description": {
"en": "Audio visualization plugin for SAO Utils",
"zh": "用于SAO Utils 的音頻可視化插件",
"zh_CN": "用于SAO Utils的音频可视化插件",
"ja_JP": "SAO Utils用の音声視覚化プラグイン"
},
"homepage": "https://nvg.dev/Mashiro_Sorata/ADV-Plugin",
"bugs": {
"email": "mashiro_sorata@qq.com",
"url": "https://nvg.dev/Mashiro_Sorata/ADV-Plugin/issues"
},
"author": {
"name": "Mashiro_Sorata",
"email": "mashiro_sorata@qq.com",
"url": "https://www.mashiros.top/"
},
"engines": {
"qt": "~5",
"qt.quick": ">=2.12",
"nvg.api": "~1"
},
"main": "module.qml",
"resources": [
{
"location": "/widget",
"catalog": "widget",
"title": {
"en": "ADV Widget",
"zh": "音頻可視化挂件",
"zh_CN": "音频可视化挂件",
"ja_JP": "音声視覚化プラグイン"
},
"preview": "preview.png",
"entry": "qml/Main.qml"
},
{
"location": "/advp-style-preset_line",
"catalog": "top.mashiros.advp-style",
"title": "Preset Line",
"entry": "./styles/Preset_line"
},
{
"location": "/advp-style-preset_waves",
"catalog": "top.mashiros.advp-style",
"title": "Preset Waves",
"entry": "./styles/Preset_waves"
},
{
"location": "/advp-style-preset-circle",
"catalog": "top.mashiros.advp-style",
"title": "Preset Circle",
"entry": "./styles/Preset_circle"
},
{
"location": "/advp-style-preset-solidcircle",
"catalog": "top.mashiros.advp-style",
"title": "Preset Solid-circle",
"entry": "./styles/Preset_solidcircle"
},
{
"location": "/advp-style-preset-ordinal_scale_ui_bottom",
"catalog": "top.mashiros.advp-style",
"title": "Preset Ordinal Scale UI bottom",
"entry": "./styles/Preset_Ordinal_Scale_UI_bottom"
},
{
"location": "/preset/advp-style-preset_line",
"catalog": "preset/widget",
"title": {
"en": "ADV Preset Line",
"zh": "ADV預置 Line",
"zh_CN": "ADV预置 Line",
"ja_JP": "ADVプリセット Line"
},
"preview": "Presets/Line/preview.png",
"entry": "Presets/Line/preset.json"
},
{
"location": "/preset/advp-style-preset_waves",
"catalog": "preset/widget",
"title": {
"en": "ADV Preset Waves",
"zh": "ADV預置 Waves",
"zh_CN": "ADV预置 Waves",
"ja_JP": "ADVプリセット Waves"
},
"preview": "Presets/Waves/preview.png",
"entry": "Presets/Waves/preset.json"
},
{
"location": "/preset/advp-style-preset_circle",
"catalog": "preset/widget",
"title": {
"en": "ADV Preset Circle",
"zh": "ADV預置 Circle",
"zh_CN": "ADV预置 Circle",
"ja_JP": "ADVプリセット Circle"
},
"preview": "Presets/Circle/preview.png",
"entry": "Presets/Circle/preset.json"
},
{
"location": "/preset/advp-style-preset_solidcircle",
"catalog": "preset/widget",
"title": {
"en": "ADV Preset Solid-circle",
"zh": "ADV預置 Solid-circle",
"zh_CN": "ADV预置 Solid-circle",
"ja_JP": "ADVプリセット Solid-circle"
},
"preview": "Presets/Solidcircle/preview.png",
"entry": "Presets/Solidcircle/preset.json"
},
{
"location": "/preset/advp-style-preset_ordinal_scale_ui_bottom",
"catalog": "preset/widget",
"title": {
"en": "ADV Preset Ordinal Scale UI bottom",
"zh": "ADV預置 序列之爭底部UI",
"zh_CN": "ADV预置 序列之争底部UI",
"ja_JP": "ADVプリセット オーディナル・スケール- UI 下部"
},
"preview": "Presets/Ordinal_Scale_UI_bottom/preview.png",
"entry": "Presets/Ordinal_Scale_UI_bottom/preset.json"
}
]
}

BIN
preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

133
qml/Common.qml

@ -0,0 +1,133 @@
pragma Singleton
import QtQml 2.2
import QtQuick 2.7
import NERvGear 1.0 as NVG
Item {
readonly property var styles: []
readonly property var stylesURL: []
readonly property var stylesCFG: []
signal audioDataUpdated(var audioData)
signal wsocketClosed()
function execute(path, args) {
path = NVG.Url.toLocalFile(Qt.resolvedUrl(path));
NVG.SystemCall.execute(path, args);
}
function openFile(fileUrl) {
let request = new XMLHttpRequest();
request.open("GET", fileUrl, false);
request.send(null);
let data = request.responseText;
request = null;
return data;
}
function parseINIString(data){
let regex = {
section: /\[\s*([^]*)\s*\]\s*$/,
param: /^\s*([\w\.\-\_]+)\s*=\s*(.*?)\s*$/,
comment: /^\s*;.*$/
};
let value = {};
let lines = data.split(/\r\n|\r|\n/);
let section = null;
let match;
lines.forEach(function(line){
if(regex.comment.test(line)){
return;
}else if(regex.param.test(line)){
match = line.match(regex.param);
if(section){
value[section][match[1]] = match[2];
}else{
value[match[1]] = match[2];
}
}else if(regex.section.test(line)){
match = line.match(regex.section);
value[match[1]] = {};
section = match[1];
}else if(line.length === 0 && section){
section = null;
};
});
return value;
}
Loader {
id: wsocket
sourceComponent: WSocket {
Component.onCompleted: {
let ini_data = openFile("../bin/advConfig.ini");
ini_data = ini_data.toLowerCase();
let cfg = parseINIString(ini_data);
if(cfg["server"]["ip"].toLowerCase() === "local") {
wsIp = "localhost";
} else {
wsIp = cfg["server"]["ip"];
}
wsPort = cfg["server"]["port"];
}}
active: false
}
function setWsocket(status) {
wsocket.active = status;
}
onWsocketClosed: {
if (wsocket.active) {
console.log("Try to reboot ADVServer...");
execute("../bin/ADVServer.exe", "-reboot");
wsocket.active = false;
wsocket.active = true;
}
}
function parse_resource(resource_list, sort) {
if (sort)
resource_list.sort(function (x, y) {
let preset_order = ["Preset Line", "Preset Waves", "Preset Circle", "Preset Solid-circle", "Preset Ordinal Scale UI bottom"];
if (preset_order.indexOf(x.title) < preset_order.indexOf(y.title))
return -1;
else if(preset_order.indexOf(x.title) > preset_order.indexOf(y.title))
return 1;
else
return 0;
});
resource_list.forEach(function (resource) {
let name = resource.title;
let styleURL = "";
let styleCFG = "";
resource.files().forEach(function (file) {
if (file.entry === "Style.qml") {
styleURL = String(file.url);
} else if (file.entry === "Config.qml") {
styleCFG = String(file.url);
}
});
if (styleURL && stylesURL.indexOf(styleURL) === -1) {
styles.push(name);
stylesURL.push(styleURL);
stylesCFG.push(styleCFG);
}
// console.log(JSON.stringify(Object.keys(resource), null, 2));
// console.log(JSON.stringify(resource.files(), null, 2));
});
}
Component.onCompleted: {
const preset_list = NVG.Resources.filter(/advp.widget.mashiros.top/, /top.mashiros.advp-style/);
parse_resource(preset_list, true);
const third_list = NVG.Resources.filter(/.*/, /top.mashiros.advp-style/);
parse_resource(third_list, false);
// console.log(JSON.stringify(styles, null, 2));
// console.log(JSON.stringify(stylesURL, null, 2));
}
}

60
qml/Main.qml

@ -0,0 +1,60 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import NERvGear 1.0 as NVG
import NERvGear.Templates 1.0 as T
import "."
T.Widget {
id: widget
solid: true
title: qsTr("ADV Widget")
resizable: true
editing: styleDialog.active
property bool initial: true
function setStyleURL(url) {
styleLoader.source = url;
}
Loader {
id: styleDialog
active: false
visible: false
sourceComponent: StylePreferences {
transientParent: widget.NVG.View.window
}
onLoaded: {
if(initial) {
styleDialog.active = false;
styleDialog.visible = true;
initial = false;
} else {
item.visible = true;
}
}
}
Loader {
id: styleLoader
active: widget.NVG.View.exposed
enabled: true
source: ""
}
menu: Menu {
Action {
text: qsTr("Settings")
onTriggered: styleDialog.active = true
}
}
Component.onCompleted: {
styleDialog.active = true;
}
}

181
qml/StylePreferences.qml

@ -0,0 +1,181 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import NERvGear 1.0 as NVG
import NERvGear.Preferences 1.0 as P
import "."
NVG.Window {
id: window
title: qsTr("ADV-Plugin: Settings")
visible: false
minimumWidth: 480
minimumHeight: 580
maximumWidth: minimumWidth
maximumHeight: minimumHeight
width: minimumWidth
height: minimumHeight
Behavior on minimumHeight {
PropertyAnimation {
duration: 500
easing.type: Easing.InOutBack
}
}
property var configuration
property var old_style_cfg
ColumnLayout {
id: root
anchors.fill: parent
anchors.margins: 16
anchors.topMargin: 0
Row {
spacing: 350
ToolButton {
text: qsTr("Save")
onClicked: {
configuration = rootPreference.save();
let index = configuration["index"];
widget.settings[Common.stylesURL[index]] = configuration[Common.stylesURL[index]];
delete configuration[Common.stylesURL[index]];
widget.settings.styles = configuration;
widget.settings.current_style = Common.stylesURL[index];
styleDialog.active = false;
}
}
ToolButton {
text: qsTr("Reset")
onClicked: {
styleLoader.load();
let cfg = rootPreference.save();
let index = cfg["index"];
widget.settings[Common.stylesURL[index]] = cfg[Common.stylesURL[index]];
widget.setStyleURL("");
widget.setStyleURL(Qt.resolvedUrl(Common.stylesURL[widget.settings.styles["index"]]));
}
}
}
Label {
Layout.alignment: Qt.AlignCenter
text: qsTr("Settings")
font.pixelSize: 24
}
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
contentWidth: preferenceLayout.implicitWidth
contentHeight: preferenceLayout.implicitHeight
ColumnLayout {
id: preferenceLayout
width: root.width
P.PreferenceGroup {
id: rootPreference
Layout.fillWidth: true
label: qsTr("Configuration")
onPreferenceEdited: {
let cfg = rootPreference.save();
// console.log(JSON.stringify(cfg, null, 2));
let index = cfg["index"];
if (widget.settings.styles["index"] !== index) {
widget.setStyleURL("");
widget.settings[Common.stylesURL[widget.settings.styles["index"]]] = old_style_cfg;
old_style_cfg = widget.settings[Common.stylesURL[index]];
}
widget.settings[Common.stylesURL[index]] = cfg[Common.stylesURL[index]];
delete cfg[Common.stylesURL[index]];
widget.settings.styles = cfg;
widget.setStyleURL(Qt.resolvedUrl(Common.stylesURL[index]));
}
P.SelectPreference {
id: styleList
name: "index"
label: qsTr("Styles")
icon.name: "solid:\uf1fc"
defaultValue: 0
model: Common.styles
}
P.Separator {}
P.PreferenceLoader {
id: styleLoader
name: Common.stylesURL[styleList.value]
source: Qt.resolvedUrl(Common.stylesCFG[styleList.value])
onLoaded: {
let cfg = save();
if (!widget.settings[Common.stylesURL[styleList.value]]) {
widget.settings[Common.stylesURL[styleList.value]] = cfg;
} else if(widget.settings[Common.stylesURL[styleList.value]]["__version"] === cfg["__version"]) {
load(widget.settings[Common.stylesURL[styleList.value]]);
} else {
widget.settings[Common.stylesURL[styleList.value]] = cfg;
}
window.minimumHeight = cfg["__cfg_height"];
}
onContentItemChanged: {
if(contentItem) {
contentItem.label = Common.styles[styleList.value];
}
}
}
P.Separator {}
Component.onCompleted: {
if(!widget.settings.styles) {
configuration = rootPreference.save();
let index = configuration["index"];
widget.settings[Common.stylesURL[index]] = configuration[Common.stylesURL[index]];
old_style_cfg = configuration[Common.stylesURL[index]];
delete configuration[Common.stylesURL[index]];
widget.settings.current_style = Common.stylesURL[index];
widget.settings.styles = configuration;
}
let index = Common.stylesURL.indexOf(widget.settings.current_style);
if (index === -1) {
index = 0;
widget.settings.current_style = Common.stylesURL[index];
}
widget.settings.styles["index"] = index;
widget.setStyleURL(Qt.resolvedUrl(Common.stylesURL[widget.settings.styles["index"]]));
rootPreference.load(widget.settings.styles);
configuration = widget.settings.styles;
old_style_cfg = widget.settings[Common.stylesURL[index]];
styleLoader.load(widget.settings[Common.stylesURL[index]]);
}
}
}
}
}
onClosing: {
widget.setStyleURL("");
widget.settings[Common.stylesURL[widget.settings.styles["index"]]] = old_style_cfg;
widget.settings.styles = configuration;
widget.setStyleURL(Qt.resolvedUrl(Common.stylesURL[widget.settings.styles["index"]]));
styleDialog.active = false;
}
}

25
qml/WSocket.qml

@ -0,0 +1,25 @@
import QtWebSockets 1.1
import "."
WebSocket {
property string wsIp: "localhost"
property int wsPort: 5050
url: "ws://" + wsIp + ":" + wsPort
active: true
onStatusChanged: {
if(status === WebSocket.Closed || status === WebSocket.Error) {
Common.wsocketClosed();
}
}
onBinaryMessageReceived: {
let arrayBuffer = new Float32Array(message);
// Common.audioData = arrayBuffer.slice();
Common.audioDataUpdated(arrayBuffer.slice());
// arrayBuffer = null;
}
}

41
qml/api/CfgAPI.qml

@ -0,0 +1,41 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import NERvGear.Preferences 1.0 as P
P.DialogPreference {
icon.name: "solid:\uf085"
live: true
property string version: ""
property int cfg_height: 580
P.TextFieldPreference {
name: "__version"
visible: false
enabled: false
defaultValue: version
}
P.SpinPreference {
name: "__cfg_height"
enabled: false
visible: false
editable: false
from: cfg_height
to: cfg_height
defaultValue: cfg_height
}
ItemDelegate {
text: qsTr("Version")
visible: version
Label {
anchors.right: parent.right
anchors.rightMargin: 16
anchors.verticalCenter: parent.verticalCenter
text: version
font.weight: Font.DemiBold
}
}
}

33
qml/api/StyleAPI.qml

@ -0,0 +1,33 @@
import QtQuick 2.12
import ".." //Common.qml
Canvas {
width: widget.width;
height: widget.height;
contextType: "2d";
renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative
signal audioDataUpdeted(var data)
signal configsUpdated()
property var configs: widget.settings[Common.stylesURL[widget.settings.styles["index"]]]
onConfigsChanged: {
if (context) {
configsUpdated();
}
}
onContextChanged: {
if (context) {
configsUpdated();
}
}
Connections {
enabled: Boolean(context)
target: Common
onAudioDataUpdated: audioDataUpdeted(audioData)
}
}

1
qml/qmldir

@ -0,0 +1 @@
singleton Common 1.0 Common.qml

65
readme.md

@ -0,0 +1,65 @@
<div align="center"><h1>ADV-Plugin</h1></div>
<div align="center"><img src="https://p.qlogo.cn/zc_icon/0/0afa95dfc4850ec9539eb0800b61a15016277179577515/0.png"></div>
<div align="center">Powered By <a href="http://mashiros.top">Mashiro_Sorata</a></div>
---
# 简介
`ADV-Plugin`是[SAO Utils 2](http://sao.gpbeta.com/)的第三方插件,可以提供系统音频数据的可视化服务。
得益于SAO Utils 2允许用户使用qml脚本编写扩展插件,相对于[第一代](https://github.com/Mashiro-Sorata/AudioDVServer-Plugin)的插件方案,第二代可以整合第一代中客户端与服务器端的功能,无需复杂的配置即可使用。
## 特色
* 整合客户端与服务器端的功能,使用更简单
* 预设4种频谱显示形式,其中每种都可以进一步自定义设置其样式
* 提供了Style的开发接口,供开发者添加更多的可视化样式
* 导入第三方Style类似其他插件,预计支持steam创意工坊下载
* 服务端崩溃后自启动
## 使用说明
安装并启用插件后,默认加载第一种预设Style样式。右键挂件可调出菜单,在挂件菜单的挂件名选单中点击Settings选项,呼出Style设置窗口。点击其中的Styles选项可切换不同的Style风格,若此Style提供可配置项,则在Styles选项下方会出现配置界面的入口。
<div align="center"><img src="https://p.qlogo.cn/zc_icon/0/5c7aacc6a0ddcb889bdacabdbf3a466716282394846386/0.png" style="zoom:80%;" /></div>
# 进阶设置
样式设置可在插件内设置,一般服务端设置采用默认设置即可,但也提供了服务端设置的接口作为高级设置。可通过更改本插件目录`bin`文件夹中的`advConfig.ini`文件来配置插件服务器与数据设置。当配置数据错误或无配置文件时使用默认值,配置值不区分大小写。其参数的具体说明如下。
- [Server]
- `ip`:可选,默认值为`local`,指代地址127.0.0.1,可更改为`any`,指代地址0.0.0.0。只支持`any`与默认参数`local`,定义插件提供服务的地址。
- `port`:可选,默认值为`5050`,定义插件提供服务的端口号。
- `maxClient`:可选,默认值为`5`,定义WebSocket的最大连接数。
- `logger`:可选,默认值为`false`。设置为`true`后可在插件所在目录下输出日志文件`ADV_Log.log`。
- [FFT]
- `attack`:可选,默认值为25。可调节频谱数据增大时的速度,该值越大,增大速度越慢。
- `decay`:可选,默认值为25。可调节频谱数据减小时的速度,该值越大,减小速度越慢。
- `norspeed`:可选,默认值为1。动态归一化系数,取值范围从1~99,该值越大,归一化的峰值数据收敛速度越快。
- `peakthr`:可选,默认值为10。归一化的峰值数据的额外增量。
- `fps`:可选,默认值为30。每秒钟数据发送的次数,**<font color='red'>必须确保该值大于5</font>**。
- `changeSpeed`:可选,默认值为25。按照`changeSpeed/fps`的比例调节频谱数据变化速度,**一般该值小于fps**。
`advConfig.ini` 文件示例:
```ini
[Server]
ip = local
port = 5050
maxClient = 5
logger = true
[FFT]
attack = 25
decay = 25
norspeed = 1
peakthr = 10
fps = 35
changeSpeed = 25
```
# 频谱样式开发
如果想开发新的频谱样式,可以参照[ADV-Plugin Wiki](https://github.com/Mashiro-Sorata/ADV-Plugin/wiki)的说明及开发教程。
欢迎开发更多有趣好玩的频谱样式,与大家分享~

111
styles/Preset_Ordinal_Scale_UI_bottom/Config.qml

@ -0,0 +1,111 @@
import QtQuick 2.12
import NERvGear.Preferences 1.0 as P
import "../../qml/api" //CfgAPI.qml
CfgAPI {
version: "1.0.0"
cfg_height: 710
P.ColorPreference {
name: "Bass Color"
label: qsTr("Bass Line Color")
defaultValue: "#DC143C"
}
P.ColorPreference {
name: "Alto Color"
label: qsTr("Alto Line Color")
defaultValue: "#F8F8FF"
}
P.ColorPreference {
name: "Treble Color"
label: qsTr("Treble Line Color")
defaultValue: "#4169E1"
}
P.Separator {}
P.SliderPreference {
name: "Bass AM"
label: qsTr("Bass Amplitude")
from: 10
to: 300
stepSize: 5
defaultValue: 100
displayValue: value + "%"
}
P.SliderPreference {
name: "Alto AM"
label: qsTr("Alto Amplitude")
from: 10
to: 300
stepSize: 5
defaultValue: 150
displayValue: value + "%"
}
P.SliderPreference {
name: "Treble AM"
label: qsTr("Treble Amplitude")
from: 10
to: 300
stepSize: 5
defaultValue: 200
displayValue: value + "%"
}
P.Separator {}
P.SliderPreference {
name: "Static AM"
label: qsTr("Static Amplitude")
from: 5
to: 100
stepSize: 1
defaultValue: 15
displayValue: value + "%"
}
P.Separator {}
P.SliderPreference {
name: "Speed"
label: qsTr("Wave Speed")
from: 1
to: 100
stepSize: 1
defaultValue: 20
displayValue: value + "%"
}
P.Separator {}
P.DialogPreference {
name: "Data Settings"
label: qsTr("Data Settings")
live: true
icon.name: "regular:\uf1de"
P.SwitchPreference {
id: _cfg_preset_osui_dataSettings_autoNormalizing
name: "Auto Normalizing"
label: qsTr("Auto Normalizing")
defaultValue: true
}
P.SpinPreference {
name: "Amplitude"
label: qsTr("Amplitude Ratio")
enabled: !_cfg_preset_osui_dataSettings_autoNormalizing.value
message: "1 to 100"
display: P.TextFieldPreference.ExpandLabel
editable: true
from: 1
to: 100
defaultValue: 10
}
}
}

123
styles/Preset_Ordinal_Scale_UI_bottom/Style.qml

File diff suppressed because one or more lines are too long

139
styles/Preset_circle/Config.qml

@ -0,0 +1,139 @@
import QtQuick 2.12
import NERvGear.Preferences 1.0 as P
import "../../qml/api" //CfgAPI.qml
CfgAPI {
version: "1.0.0"
cfg_height: 740
P.ColorPreference {
name: "Main Color"
label: qsTr("Spectrum Line Color")
defaultValue: "#FF4500"
}
P.SelectPreference {
name: "Line Position"
label: qsTr("Spectrum Line Position")
defaultValue: 0
model: [qsTr("Both"), qsTr("Outside"), qsTr("Inside")]
}
P.SliderPreference {
name: "Line Width"
label: qsTr("Spectrum Line Width")
from: 0.1
to: 10
stepSize: 0.1
defaultValue: 1
displayValue: value.toFixed(1) + "px"
}
P.SliderPreference {
name: "Max Range"
label: qsTr("Max Amplitude")
from: 0
to: 100
stepSize: 1
defaultValue: 50
displayValue: value + "%"
}
P.SelectPreference {
name: "Data Length"
label: qsTr("Spectrum Length")
defaultValue: 0
model: [64, 32, 16, 8]
}
P.Separator {}
P.SpinPreference {
name: "Channel"
label: qsTr("Channel")
message: "1 to 2"
display: P.TextFieldPreference.ExpandLabel
editable: false
from: 1
to: 2
defaultValue: 2
}
P.Separator {}
P.SwitchPreference {
name: "Reverse"
label: qsTr("Reverse Spectrum")
defaultValue: false
}
P.Separator {}
P.SwitchPreference {
id: _cfg_preset_line_rotate
name: "Rotate"
label: qsTr("Auto Rotate")
defaultValue: false
}
P.SliderPreference {
name: "Ratate Speed"
label: qsTr("Ratate Speed")
enabled: _cfg_preset_line_rotate.value
from: 1
to: 100
stepSize: 1
defaultValue: 10
displayValue: value + "%"
}
P.SpinPreference {
name: "Angle"
label: qsTr("Initial Angle")
message: "0 to 359"
enabled: !_cfg_preset_line_rotate.value
display: P.TextFieldPreference.ExpandLabel
editable: true
from: 0
to: 359
defaultValue: 0
}
P.Separator {}
P.DialogPreference {
name: "Data Settings"
label: qsTr("Data Settings")
live: true
icon.name: "regular:\uf1de"
P.SwitchPreference {
id: _cfg_preset_circle_dataSettings_autoNormalizing
name: "Auto Normalizing"
label: qsTr("Auto Normalizing")
defaultValue: true
}
P.SpinPreference {
name: "Amplitude"
label: qsTr("Amplitude Ratio")
enabled: !_cfg_preset_circle_dataSettings_autoNormalizing.value
message: "1 to 100"
display: P.TextFieldPreference.ExpandLabel
editable: true
from: 1
to: 100
defaultValue: 10
}
P.Separator {}
P.SelectPreference {
name: "Unit Style"
label: qsTr("Display Style")
defaultValue: 0
model: [qsTr("Linear"), qsTr("Decibel")]
}
}
}

132
styles/Preset_circle/Style.qml

@ -0,0 +1,132 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import "../../qml/api"
StyleAPI {
readonly property var audioData: new Array(128)
//configs
readonly property string color: configs["Main Color"]
readonly property int linePosition: configs["Line Position"]
readonly property real lineWidth: configs["Line Width"]
readonly property real maxRange: configs["Max Range"] / 100
readonly property int uDataLen: Math.pow(2, configs["Data Length"])
readonly property int dataLength: 64/uDataLen
readonly property int channel: configs["Channel"]
readonly property bool reverse: configs["Reverse"]
readonly property bool rotateFlag: configs["Rotate"]
readonly property real rSpeed: configs["Ratate Speed"] / 100
readonly property real angle: configs["Angle"]
readonly property bool autoNormalizing: configs["Data Settings"]["Auto Normalizing"]
readonly property real amplitude: configs["Data Settings"]["Amplitude"] / 400
readonly property int unitStyle: configs["Data Settings"]["Unit Style"]
readonly property int total: channel*dataLength
readonly property real dotGap: 360/total
property real offsetAngle: 0
property var outerPos: []
property var innerPos: []
readonly property real degUnit: Math.PI/180
readonly property real subRatio: 0.2*maxRange
readonly property real mainRatio: 1-subRatio*2.5
readonly property real minLength: Math.min(width, height)
readonly property real ratio:minLength*subRatio
readonly property real halfWidth: width/2
readonly property real halfHeight: height/2
readonly property real halfMinLength: minLength/2
readonly property real logAmplitude: Math.log10(amplitude)
onConfigsUpdated: {
context.lineWidth = lineWidth;
context.strokeStyle = color;
}
function getPos(r, deg) {
return [halfWidth+Math.cos(deg)*r,halfHeight+Math.sin(deg)*r];
}
function createPoint() {
outerPos = [];
innerPos = [];
let deg, deltaR, r1, r2, _rhmLen;
_rhmLen = mainRatio*halfMinLength;
for (let j=0; j < channel; j++) {
for (let i=0; i < dataLength; i++) {
deg = degUnit*((i+j*dataLength)*dotGap + offsetAngle);
deltaR = audioData[reverse*(dataLength-i-1)+(!reverse)*(i+j*dataLength)] * ratio;
r1 = _rhmLen+1+deltaR*(linePosition!==2);
r2 = _rhmLen-1-deltaR*(linePosition!==1);
outerPos.push(getPos(r1, deg));
innerPos.push(getPos(r2, deg));
}
}
offsetAngle = rotateFlag ? ((offsetAngle + rSpeed) % 360) : angle;
}
onAudioDataUpdeted: {
if(autoNormalizing) {
if (unitStyle) {
//
let logPeak = Math.log10(data[128]);
for(let i=0; i<total; i++) {
audioData[i] = 0;
for(let j=0; j<uDataLen; j++) {
audioData[i] += Math.max(0, 0.4 * (Math.log10(data[i*uDataLen+j])-logPeak) + 1.0);
}
audioData[i] /= uDataLen;
}
} else {
//线
for(let i=0; i<total; i++) {
audioData[i] = 0;
for(let j=0; j<uDataLen; j++) {
audioData[i] += data[i*uDataLen+j] / data[128];
}
audioData[i] /= uDataLen;
}
}
} else {
if (unitStyle) {
//
for(let i=0; i<total; i++) {
audioData[i] = 0;
for(let j=0; j<uDataLen; j++) {
audioData[i] += Math.max(0, 0.35 * (Math.log10(data[i*uDataLen+j])+logAmplitude) + 1.0);
}
audioData[i] /= uDataLen;
}
} else {
//线
for(let i=0; i<total; i++) {
audioData[i] = 0;
for(let j=0; j<uDataLen; j++) {
audioData[i] += data[i*uDataLen+j] * amplitude;
}
audioData[i] /= uDataLen;
}
}
}
context.clearRect(0, 0, width, height);
createPoint();
context.beginPath();
for(let i=0; i<total; i++) {
context.moveTo(outerPos[i][0], outerPos[i][1]);
context.lineTo(innerPos[i][0], innerPos[i][1]);
}
context.stroke();
requestPaint();
}
Component.onCompleted: {
for (let i = 0; i < 128; i++) {
audioData[i] = 0;
}
}
}

150
styles/Preset_line/Config.qml

@ -0,0 +1,150 @@
import QtQuick 2.12
import NERvGear.Preferences 1.0 as P
import "../../qml/api" //CfgAPI.qml
CfgAPI {
version: "1.0.0"
cfg_height: 580
P.SwitchPreference {
id: _cfg_preset_line_Center_Line
name: "Center Line"
label: qsTr("Show Center Line")
defaultValue: true
}
P.ColorPreference {
name: "Center Color"
label: qsTr("Center Line Color")
enabled: _cfg_preset_line_Center_Line.value
defaultValue: "#FF4500"
}
P.Separator {}
P.ColorPreference {
name: "Line Color"
label: qsTr("Spectrum Line Color")
defaultValue: "#FF4500"
}
P.SelectPreference {
name: "Line Position"
label: qsTr("Spectrum Line Position")
defaultValue: 0
model: [qsTr("Both"), qsTr("Up"), qsTr("Down")]
}
P.SelectPreference {
name: "Data Length"
label: qsTr("Spectrum Length")
defaultValue: 0
model: [64, 32, 16, 8]
}
P.Separator {}
P.SpinPreference {
id: _cfg_preset_line_Channel
name: "Channel"
label: qsTr("Channel")
message: "1 to 2"
display: P.TextFieldPreference.ExpandLabel
editable: false
from: 1
to: 2
defaultValue: 2
}
P.SwitchPreference {
name: "Reverse"
label: qsTr("Reverse Spectrum")
enabled: _cfg_preset_line_Channel.value === 1
defaultValue: false
}
P.Separator {}
P.DialogPreference {
name: "Rotate Settings"
label: qsTr("Rotate Settings")
live: true
icon.name: "regular:\uf1de"
P.SwitchPreference {
id: _cfg_preset_line_Rotate_Center_Enable
name: "Center Enable"
label: qsTr("Rotate Center Line")
defaultValue: false
}
P.SliderPreference {
name: "Center Angle"
label: qsTr("Angle of Center Line")
enabled: _cfg_preset_line_Rotate_Center_Enable.value
from: -30
to: 30
stepSize: 1
defaultValue: 10
displayValue: value + "°"
}
P.Separator {}
P.SwitchPreference {
id: _cfg_preset_line_Rotate_Line_Enable
name: "Line Enable"
label: qsTr("Rotate Spectrum Line")
defaultValue: false
}
P.SliderPreference {
name: "Line Angle"
label: qsTr("Angle of Spectrum Line")
enabled: _cfg_preset_line_Rotate_Line_Enable.value
from: -30
to: 30
stepSize: 1
defaultValue: 10
displayValue: value + "°"
}
}
P.Separator {}
P.DialogPreference {
name: "Data Settings"
label: qsTr("Data Settings")
live: true
icon.name: "regular:\uf1de"
P.SwitchPreference {
id: _cfg_preset_line_dataSettings_autoNormalizing
name: "Auto Normalizing"
label: qsTr("Auto Normalizing")
defaultValue: true
}
P.SpinPreference {
name: "Amplitude"
label: qsTr("Amplitude Ratio")
enabled: !_cfg_preset_line_dataSettings_autoNormalizing.value
message: "1 to 100"
display: P.TextFieldPreference.ExpandLabel
editable: true
from: 1
to: 100
defaultValue: 10
}
P.Separator {}
P.SelectPreference {
name: "Unit Style"
label: qsTr("Display Style")
defaultValue: 0
model: [qsTr("Linear"), qsTr("Decibel")]
}
}
}

163
styles/Preset_line/Style.qml

@ -0,0 +1,163 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import "../../qml/api"
StyleAPI {
readonly property var audioData: new Array(128)
readonly property bool centerLineFlag: configs["Center Line"]