// // AudioScope.qml // // Created by Luis Cuenca on 11/22/2017 // Copyright 2017 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 import QtQuick.Controls 1.4 import "styles-uit" import "controls-uit" as HifiControlsUit Item { id: root width: parent.width height: parent.height property var _scopeInputData property var _scopeOutputLeftData property var _scopeOutputRightData property var _triggerInputData property var _triggerOutputLeftData property var _triggerOutputRightData property var _triggerValues: QtObject{ property int x: parent.width/2 property int y: parent.height/3 } property var _triggered: false property var _steps property var _refreshMs: 32 property var _framesPerSecond: AudioScope.getFramesPerSecond() property var _isFrameUnits: true property var _holdStart: QtObject{ property int x: 0 property int y: 0 } property var _holdEnd: QtObject{ property int x: 0 property int y: 0 } property var _timeBeforeHold: 300 property var _pressedTime: 0 property var _isPressed: false property var _recOpacity : 0.0 property var _recSign : 0.05 property var _outputLeftState: false property var _outputRightState: false property var _wavFilePath: "" function isHolding() { return (_pressedTime > _timeBeforeHold); } function updateMeasureUnits() { timeButton.text = _isFrameUnits ? "Display Frames" : "Milliseconds"; fiveLabel.text = _isFrameUnits ? "5" : "" + (Math.round(1000 * 5.0/_framesPerSecond)); twentyLabel.text = _isFrameUnits ? "20" : "" + (Math.round(1000 * 20.0/_framesPerSecond)); fiftyLabel.text = _isFrameUnits ? "50" : "" + (Math.round(1000 * 50.0/_framesPerSecond)); } function collectScopeData() { if (inputCh.checked) { _scopeInputData = AudioScope.scopeInput; } if (outputLeftCh.checked) { _scopeOutputLeftData = AudioScope.scopeOutputLeft; } if (outputRightCh.checked) { _scopeOutputRightData = AudioScope.scopeOutputRight; } } function collectTriggerData() { if (inputCh.checked) { _triggerInputData = AudioScope.triggerInput; } if (outputLeftCh.checked) { _triggerOutputLeftData = AudioScope.triggerOutputLeft; } if (outputRightCh.checked) { _triggerOutputRightData = AudioScope.triggerOutputRight; } } function setRecordingLabelOpacity(opacity) { _recOpacity = opacity; recCircle.opacity = _recOpacity; recText.opacity = _recOpacity; } function updateRecordingLabel() { _recOpacity += _recSign; if (_recOpacity > 1.0 || _recOpacity < 0.0) { _recOpacity = _recOpacity > 1.0 ? 1.0 : 0.0; _recSign *= -1; } setRecordingLabelOpacity(_recOpacity); } function pullFreshValues() { if (AudioScriptingInterface.getRecording()) { updateRecordingLabel(); } if (!AudioScope.getPause()) { if (!_triggered) { collectScopeData(); } } if (inputCh.checked || outputLeftCh.checked || outputRightCh.checked) { mycanvas.requestPaint(); } } function startRecording() { _wavFilePath = (new Date()).toISOString(); // yyyy-mm-ddThh:mm:ss.sssZ _wavFilePath = _wavFilePath.replace(/[\-:]|\.\d*Z$/g, "").replace("T", "-") + ".wav"; // Using controller recording default directory _wavFilePath = Recording.getDefaultRecordingSaveDirectory() + _wavFilePath; if (!AudioScriptingInterface.startRecording(_wavFilePath)) { Messages.sendMessage("Hifi-Notifications", JSON.stringify({message:"Error creating: "+_wavFilePath})); updateRecordingUI(false); } } function stopRecording() { AudioScriptingInterface.stopRecording(); setRecordingLabelOpacity(0.0); Messages.sendMessage("Hifi-Notifications", JSON.stringify({message:"Saved: "+_wavFilePath})); } function updateRecordingUI(isRecording) { if (!isRecording) { recordButton.text = "Record"; recordButton.color = hifi.buttons.black; outputLeftCh.checked = _outputLeftState; outputRightCh.checked = _outputRightState; } else { recordButton.text = "Stop"; recordButton.color = hifi.buttons.red; _outputLeftState = outputLeftCh.checked; _outputRightState = outputRightCh.checked; outputLeftCh.checked = true; outputRightCh.checked = true; } } function toggleRecording() { if (AudioScriptingInterface.getRecording()) { updateRecordingUI(false); stopRecording(); } else { updateRecordingUI(true); startRecording(); } } Timer { interval: _refreshMs; running: true; repeat: true onTriggered: pullFreshValues() } Canvas { id: mycanvas anchors.fill:parent onPaint: { function displayMeasureArea(ctx) { ctx.fillStyle = Qt.rgba(0.1, 0.1, 0.1, 1); ctx.fillRect(_holdStart.x, 0, _holdEnd.x - _holdStart.x, height); ctx.lineWidth = "2"; ctx.strokeStyle = "#555555"; ctx.beginPath(); ctx.moveTo(_holdStart.x, 0); ctx.lineTo(_holdStart.x, height); ctx.moveTo(_holdEnd.x, 0); ctx.lineTo(_holdEnd.x, height); ctx.moveTo(_holdStart.x, _holdStart.y); ctx.lineTo(_holdEnd.x, _holdStart.y); ctx.moveTo(_holdEnd.x, _holdEnd.y); ctx.lineTo(_holdStart.x, _holdEnd.y); ctx.stroke(); } function displayTrigger(ctx, lineWidth, color) { var crossSize = 3; var holeSize = 2; ctx.lineWidth = lineWidth; ctx.strokeStyle = color; ctx.beginPath(); ctx.moveTo(_triggerValues.x - (crossSize + holeSize), _triggerValues.y); ctx.lineTo(_triggerValues.x - holeSize, _triggerValues.y); ctx.moveTo(_triggerValues.x + holeSize, _triggerValues.y); ctx.lineTo(_triggerValues.x + (crossSize + holeSize), _triggerValues.y); ctx.moveTo(_triggerValues.x, _triggerValues.y - (crossSize + holeSize)); ctx.lineTo(_triggerValues.x, _triggerValues.y - holeSize); ctx.moveTo(_triggerValues.x, _triggerValues.y + holeSize); ctx.lineTo(_triggerValues.x, _triggerValues.y + (crossSize + holeSize)); ctx.stroke(); } function displayBackground(ctx, datawidth, steps, lineWidth, color) { var verticalPadding = 100; ctx.strokeStyle = color; ctx.lineWidth = lineWidth; ctx.moveTo(0, height/2); ctx.lineTo(datawidth, height/2); var gap = datawidth/steps; for (var i = 0; i < steps; i++) { ctx.moveTo(i*gap + 1, verticalPadding); ctx.lineTo(i*gap + 1, height-verticalPadding); } ctx.moveTo(datawidth-1, verticalPadding); ctx.lineTo(datawidth-1, height-verticalPadding); ctx.stroke(); } function drawScope(ctx, data, width, color) { ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = width; var x = 0; for (var i = 0; i < data.length-1; i++) { ctx.moveTo(x, data[i] + height/2); ctx.lineTo(++x, data[i+1] + height/2); } ctx.stroke(); } function getMeasurementText(dist) { var datasize = _scopeInputData.length; var value = 0; if (fiveFrames.checked) { value = (_isFrameUnits) ? 5.0*dist/datasize : (Math.round(1000 * 5.0/_framesPerSecond))*dist/datasize; } else if (twentyFrames.checked) { value = (_isFrameUnits) ? 20.0*dist/datasize : (Math.round(1000 * 20.0/_framesPerSecond))*dist/datasize; } else if (fiftyFrames.checked) { value = (_isFrameUnits) ? 50.0*dist/datasize : (Math.round(1000 * 50.0/_framesPerSecond))*dist/datasize; } value = Math.abs(Math.round(value*100)/100); var measureText = "" + value + (_isFrameUnits ? " frames" : " milliseconds"); return measureText; } function drawMeasurements(ctx, color) { ctx.fillStyle = color; ctx.font = "normal 16px sans-serif"; var fontwidth = 8; var measureText = getMeasurementText(_holdEnd.x - _holdStart.x); if (_holdStart.x < _holdEnd.x) { ctx.fillText("" + height/2 - _holdStart.y, _holdStart.x-40, _holdStart.y); ctx.fillText("" + height/2 - _holdEnd.y, _holdStart.x-40, _holdEnd.y); ctx.fillText(measureText, _holdEnd.x+10, _holdEnd.y); } else { ctx.fillText("" + height/2 - _holdStart.y, _holdStart.x+10, _holdStart.y); ctx.fillText("" + height/2 - _holdEnd.y, _holdStart.x+10, _holdEnd.y); ctx.fillText(measureText, _holdEnd.x-fontwidth*measureText.length, _holdEnd.y); } } var ctx = getContext("2d"); ctx.fillStyle = Qt.rgba(0, 0, 0, 1); ctx.fillRect(0, 0, width, height); if (isHolding()) { displayMeasureArea(ctx); } var guideLinesColor = "#555555" var guideLinesWidth = "1" displayBackground(ctx, _scopeInputData.length, _steps, guideLinesWidth, guideLinesColor); var triggerWidth = "3" var triggerColor = "#EFB400" if (AudioScope.getAutoTrigger()) { displayTrigger(ctx, triggerWidth, triggerColor); } var scopeWidth = "2" var scopeInputColor = "#00B4EF" var scopeOutputLeftColor = "#BB0000" var scopeOutputRightColor = "#00BB00" if (!_triggered) { if (inputCh.checked) { drawScope(ctx, _scopeInputData, scopeWidth, scopeInputColor); } if (outputLeftCh.checked) { drawScope(ctx, _scopeOutputLeftData, scopeWidth, scopeOutputLeftColor); } if (outputRightCh.checked) { drawScope(ctx, _scopeOutputRightData, scopeWidth, scopeOutputRightColor); } } else { if (inputCh.checked) { drawScope(ctx, _triggerInputData, scopeWidth, scopeInputColor); } if (outputLeftCh.checked) { drawScope(ctx, _triggerOutputLeftData, scopeWidth, scopeOutputLeftColor); } if (outputRightCh.checked) { drawScope(ctx, _triggerOutputRightData, scopeWidth, scopeOutputRightColor); } } if (isHolding()) { drawMeasurements(ctx, "#eeeeee"); } if (_isPressed) { _pressedTime += _refreshMs; } } } MouseArea { id: hitbox anchors.fill: mycanvas hoverEnabled: true onPressed: { _isPressed = true; _pressedTime = 0; _holdStart.x = mouseX; _holdStart.y = mouseY; } onPositionChanged: { _holdEnd.x = mouseX; _holdEnd.y = mouseY; } onReleased: { if (!isHolding() && AudioScope.getAutoTrigger()) { _triggerValues.x = mouseX _triggerValues.y = mouseY AudioScope.setTriggerValues(mouseX, mouseY-height/2); } _isPressed = false; _pressedTime = 0; } } HifiControlsUit.CheckBox { id: activated boxSize: 20 anchors.top: parent.top; anchors.left: parent.left; anchors.topMargin: 8; anchors.leftMargin: 20; checked: AudioScope.getVisible(); onCheckedChanged: { AudioScope.setVisible(checked); activelabel.text = AudioScope.getVisible() ? "On" : "Off" } } HifiControlsUit.Label { id: activelabel text: AudioScope.getVisible() ? "On" : "Off" anchors.top: activated.top; anchors.left: activated.right; } HifiControlsUit.CheckBox { id: outputLeftCh boxSize: 20 text: "Output L" anchors.horizontalCenter: parent.horizontalCenter; anchors.top: parent.top; anchors.topMargin: 8; onCheckedChanged: { AudioScope.setServerEcho(outputLeftCh.checked || outputRightCh.checked); } } HifiControlsUit.Label { text: "Channels"; anchors.horizontalCenter: outputLeftCh.horizontalCenter; anchors.bottom: outputLeftCh.top; anchors.bottomMargin: 8; } HifiControlsUit.CheckBox { id: inputCh boxSize: 20 text: "Input Mono" anchors.bottom: outputLeftCh.bottom; anchors.right: outputLeftCh.left; anchors.rightMargin: 40; onCheckedChanged: { AudioScope.setLocalEcho(checked); } } HifiControlsUit.CheckBox { id: outputRightCh boxSize: 20 text: "Output R" anchors.bottom: outputLeftCh.bottom; anchors.left: outputLeftCh.right; anchors.leftMargin: 40; onCheckedChanged: { AudioScope.setServerEcho(outputLeftCh.checked || outputRightCh.checked); } } HifiControlsUit.Button { id: recordButton; text: "Record"; color: hifi.buttons.black; colorScheme: hifi.colorSchemes.dark; anchors.right: parent.right; anchors.bottom: parent.bottom; anchors.rightMargin: 30; anchors.bottomMargin: 8; width: 95; height: 55; onClicked: { toggleRecording(); } } HifiControlsUit.Button { id: pauseButton; color: hifi.buttons.black; colorScheme: hifi.colorSchemes.dark; anchors.right: recordButton.left; anchors.bottom: parent.bottom; anchors.rightMargin: 30; anchors.bottomMargin: 8; height: 55; width: 95; text: " Pause "; onClicked: { AudioScope.togglePause(); } } HifiControlsUit.CheckBox { id: twentyFrames boxSize: 20 anchors.left: parent.horizontalCenter; anchors.bottom: parent.bottom; anchors.bottomMargin: 8; onCheckedChanged: { if (checked){ fiftyFrames.checked = false; fiveFrames.checked = false; AudioScope.selectAudioScopeTwentyFrames(); _steps = 20; AudioScope.setPause(false); } } } HifiControlsUit.Label { id:twentyLabel anchors.left: twentyFrames.right; anchors.verticalCenter: twentyFrames.verticalCenter; } HifiControlsUit.Button { id: timeButton; color: hifi.buttons.black; colorScheme: hifi.colorSchemes.dark; text: "Display Frames"; anchors.horizontalCenter: twentyFrames.horizontalCenter; anchors.bottom: twentyFrames.top; anchors.bottomMargin: 8; height: 26; onClicked: { _isFrameUnits = !_isFrameUnits; updateMeasureUnits(); } } HifiControlsUit.CheckBox { id: fiveFrames boxSize: 20 anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom; anchors.bottomMargin: 8; anchors.horizontalCenterOffset: -50; checked: true; onCheckedChanged: { if (checked) { fiftyFrames.checked = false; twentyFrames.checked = false; AudioScope.selectAudioScopeFiveFrames(); _steps = 5; AudioScope.setPause(false); } } } HifiControlsUit.Label { id:fiveLabel anchors.left: fiveFrames.right; anchors.verticalCenter: fiveFrames.verticalCenter; } HifiControlsUit.CheckBox { id: fiftyFrames boxSize: 20 anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom; anchors.bottomMargin: 8; anchors.horizontalCenterOffset: 70; onCheckedChanged: { if (checked) { twentyFrames.checked = false; fiveFrames.checked = false; AudioScope.selectAudioScopeFiftyFrames(); _steps = 50; AudioScope.setPause(false); } } } HifiControlsUit.Label { id:fiftyLabel anchors.left: fiftyFrames.right; anchors.verticalCenter: fiftyFrames.verticalCenter; } HifiControlsUit.Switch { id: triggerSwitch; height: 26; anchors.left: parent.left; anchors.bottom: parent.bottom; anchors.leftMargin: 75; anchors.bottomMargin: 8; labelTextOff: "Off"; labelTextOn: "On"; onCheckedChanged: { if (!checked) AudioScope.setPause(false); AudioScope.setPause(false); AudioScope.setAutoTrigger(checked); AudioScope.setTriggerValues(_triggerValues.x, _triggerValues.y-root.height/2); } } HifiControlsUit.Label { text: "Trigger"; anchors.left: triggerSwitch.left; anchors.leftMargin: -15; anchors.bottom: triggerSwitch.top; } Rectangle { id: recordIcon; width:110; height:40; anchors.right: parent.right; anchors.top: parent.top; anchors.topMargin: 8; color: "transparent" Text { id: recText text: "REC" color: "red" font.pixelSize: 30; anchors.left: recCircle.right; anchors.leftMargin: 10; opacity: _recOpacity; y: -8; } Rectangle { id: recCircle; width: 25; height: 25; radius: width*0.5 opacity: _recOpacity; color: "red"; } } Component.onCompleted: { _steps = AudioScope.getFramesPerScope(); AudioScope.setTriggerValues(_triggerValues.x, _triggerValues.y-root.height/2); activated.checked = true; inputCh.checked = true; updateMeasureUnits(); } Connections { target: AudioScope onPauseChanged: { if (!AudioScope.getPause()) { pauseButton.text = "Pause"; pauseButton.color = hifi.buttons.black; AudioScope.setTriggered(false); _triggered = false; } else { pauseButton.text = "Continue"; pauseButton.color = hifi.buttons.blue; } } onTriggered: { _triggered = true; collectTriggerData(); AudioScope.setPause(true); } } }