From 201d18d2e6b9011b47020bd2c6acbfd71cc68b40 Mon Sep 17 00:00:00 2001 From: GhostNaN <59353890+GhostNaN@users.noreply.github.com> Date: Tue, 31 Dec 2019 21:01:24 -0500 Subject: [PATCH] Upload main Windows files --- ReVidiaGUI_win.pyw | 806 +++++++++++++++++++++++++++++++++++++++++++++ ReVidiaQT_win.py | 152 +++++++++ WinReadMe.txt | 90 +++++ install.bat | 22 ++ 4 files changed, 1070 insertions(+) create mode 100644 ReVidiaGUI_win.pyw create mode 100644 ReVidiaQT_win.py create mode 100644 WinReadMe.txt create mode 100644 install.bat diff --git a/ReVidiaGUI_win.pyw b/ReVidiaGUI_win.pyw new file mode 100644 index 0000000..9d0b9cd --- /dev/null +++ b/ReVidiaGUI_win.pyw @@ -0,0 +1,806 @@ +#!venv/Scripts/pythonw +# -*- coding: utf-8 -*- + +import sys +import re +import time +import queue +import threading as th +from ReVidiaQT_win import * +from PyQt5.QtCore import Qt +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +# Windows Icon fix +import ctypes +ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('r.r.r.r') + + +# Create the self object and main window +class ReVidiaMain(QMainWindow): + def __init__(self, parent=None): + super(ReVidiaMain, self).__init__(parent) + + # Sets up window to be in the middle and to be half screen height + screen = QApplication.desktop().screenNumber( + QApplication.desktop().cursor().pos()) + screenSize = QApplication.desktop().screenGeometry(screen) + self.width = screenSize.width() // 2 + self.height = screenSize.height() // 2 + self.left = screenSize.center().x() - self.width // 2 + self.top = screenSize.center().y() - self.height // 2 + + # Default variables + self.title = 'ReVidia' + self.split = 0 + self.frameRate = 100 + self.interp = 2 + self.audioFrames = 4096 + self.backgroundColor = QColor(50, 50, 50, 255) + self.barColor = QColor(255, 255, 255, 255) # R, G, B, Alpha 0-255 + self.outlineColor = QColor(0, 0, 0) + self.textPalette = QPalette() + self.barHeight = 0.001 + self.outlineThick = 0 + self.barWidth = 14 + self.gapWidth = 6 + self.wholeWidth = self.barWidth + self.gapWidth + self.checkRainbow = 0 + self.outline = 0 + self.cutout = 0 + self.checkNotes = 0 + self.checkLateNum = 0 + self.checkBarNum = 0 + self.checkLatency = 0 + self.checkDB = 0 + + self.initUI() + + # Setup main window + def initUI(self): + self.setWindowIcon(QIcon('docs/REV.png')) + self.setWindowTitle(self.title) + self.setGeometry(self.left, self.top, self.width, self.height) + self.setAttribute(Qt.WA_TranslucentBackground, True) # Initial background is transparent + self.setWindowFlags(Qt.FramelessWindowHint) + self.setTextPalette() + self.PA = getPA() # Initialize PortAudio + self.getDevice(True) # Get Device before starting + + # Setup menu bar + mainBar = self.menuBar() + mainMenu = mainBar.addMenu('Main') + mainMenu.setToolTipsVisible(True) + designMenu = mainBar.addMenu('Design') + designMenu.setToolTipsVisible(True) + statsMenu = mainBar.addMenu('Stats') + statsMenu.setToolTipsVisible(True) + + viewDevices = QAction('Device', self) + viewDevices.setToolTip('Select Audio Device') + viewDevices.triggered.connect(self.getDevice) + + split = QAction('Split Audio', self) + split.setCheckable(True) + split.setToolTip('Toggle to Split Audio Channels') + split.toggled.connect(self.setSplit) + + interpMenu = QMenu('Interpolation', self) + interpMenu.setToolTip('Set Interp Amount (Smoothing)') + interpSettings = ['No Interpolation', 'Low', 'Mid', 'High'] + interp = 0 + self.interpDict = {} + for f in range(4): + self.interpDict[str(interp)] = QAction(interpSettings[f], self) + self.interpDict[str(interp)].setCheckable(True) + self.interpDict[str(interp)].triggered.connect(lambda checked, index=interp: self.setInterp(index)) + interpMenu.addAction(self.interpDict[str(interp)]) + interp += 1 + self.interpDict[str(self.interp)].setChecked(True) + + frames = QMenu('Frames', self) + frames.setToolTip('Set the Audio Frame Rate') + audioRate = 1024 + self.audioFramesDict = {} + for f in range(4): + self.audioFramesDict[str(audioRate)] = QAction(str(audioRate), self) + self.audioFramesDict[str(audioRate)].setCheckable(True) + self.audioFramesDict[str(audioRate)].triggered.connect(lambda checked, index=audioRate: self.setFrames(index)) + frames.addAction(self.audioFramesDict[str(audioRate)]) + audioRate *= 2 + self.audioFramesDict[str(self.audioFrames)].setChecked(True) + + color = QMenu('Color', self) + color.setToolTip('Select Colors and Transparency') + barColor = QAction('Bar Color', self) + barColor.triggered.connect(self.setBarColor) + backColor = QAction('Background Color', self) + backColor.triggered.connect(self.setBackgroundColor) + outColor = QAction('Outline Color', self) + outColor.triggered.connect(self.setOutlineColor) + rainbowColor = QAction('Rainbow', self) + rainbowColor.setCheckable(True) + rainbowColor.triggered.connect(self.setRainbow) + color.addAction(barColor) + color.addAction(backColor) + color.addAction(outColor) + color.addAction(rainbowColor) + + barSize = QAction('Dimensions', self) + barSize.setCheckable(True) + barSize.setToolTip('Change the Bars Dimensions') + barSize.toggled.connect(self.showBarSize) + + outline = QAction('Outline Only', self) + outline.setCheckable(True) + outline.setToolTip('Toggle Outline/Turn Off Fill') + outline.toggled.connect(self.setOutline) + + cutout = QAction('Cutout', self) + cutout.setCheckable(True) + cutout.setToolTip('Toggle to Cutout Background with Bars') + cutout.toggled.connect(self.setCutout) + + notes = QAction('Notes', self) + notes.setCheckable(True) + notes.setToolTip('Guesses the Notes') + notes.toggled.connect(self.showNotes) + + lateNum = QAction('Late Frames', self) + lateNum.setCheckable(True) + lateNum.setToolTip('Display Amount of Late Video Frames') + lateNum.toggled.connect(self.showLateFrames) + + barNum = QAction('Bars', self) + barNum.setCheckable(True) + barNum.setToolTip('Display Amount of Bars') + barNum.toggled.connect(self.showBarNum) + + latNum = QAction('Latency', self) + latNum.setCheckable(True) + latNum.setToolTip('Display Latency Between Display and Audio') + latNum.toggled.connect(self.showLatency) + + dbBar = QAction('dB Bar', self) + dbBar.setCheckable(True) + dbBar.setToolTip('Display dB Bar Indicating Volume') + dbBar.toggled.connect(self.showDB) + + fpsSpinBox = QSpinBox() + fpsSpinBox.setRange(1, 999) + fpsSpinBox.setSuffix(' FPS') + fpsSpinBox.setMaximumWidth(70) + fpsSpinBox.setMaximumHeight(20) + fpsSpinBox.valueChanged[int].connect(self.setFPS) + fpsSpinBox.setValue(self.frameRate) + fpsSpinBox.setKeyboardTracking(False) + self.fpsDock = QDockWidget(self) + self.fpsDock.move(150, -20) + self.fpsDock.setFeatures(QDockWidget.NoDockWidgetFeatures) + self.fpsDock.setWidget(fpsSpinBox) + self.fpsDock.show() + + mainMenu.addAction(viewDevices) + mainMenu.addAction(split) + mainMenu.addMenu(interpMenu) + mainMenu.addMenu(frames) + designMenu.addMenu(color) + designMenu.addAction(barSize) + designMenu.addAction(outline) + designMenu.addAction(cutout) + statsMenu.addAction(notes) + statsMenu.addAction(lateNum) + statsMenu.addAction(barNum) + statsMenu.addAction(latNum) + statsMenu.addAction(dbBar) + + # Fixes Windows window bug + self.setWindowFlags(self.windowFlags() & ~Qt.FramelessWindowHint) + self.show() + self.startStream() + + # Audio Data Collection + def startStream(self): + self.sampleRate = sampleRate(self.PA, self.ID) + self.stream = startStream(self.PA, self.ID, self.sampleRate) + self.startVidia() + + # Main loop of ReVidia + def startVidia(self): + # Create separate thread for audio data collection + self.Q1 = queue.SimpleQueue() + self.Q2 = queue.SimpleQueue() + self.T1 = th.Thread(target=audioData, args=(self.Q1, self.Q2, self.stream, self.audioFrames, self.split)) + self.T1.daemon = True + self.T1.start() + + self.dataList = [] + self.barValues = [0] + self.splitBarValues = [0] + + while True: + timeD = time.time() + self.widthSize = self.size().width() // self.wholeWidth + + self.updateObjects() + QApplication.processEvents() # This is NEEDED to stop gui freezing + self.prePaint() + self.repaint() + + self.latency = round((time.time() - timeD) * 1000) + + # Frame Time Delay Scalar + delay = (1 / self.frameRate) + frameTime = delay - (time.time() - timeD) + if frameTime < 0: + frameTime = 0 + if self.checkLateNum: + self.lateFrames += 1 + time.sleep(frameTime) + + # Gets audio devices + def getDevice(self, firstRun): + deviceList = (deviceName(self.PA)) + device, ok = QInputDialog.getItem(self, "ReVidia", "Select Audio Input Device:", deviceList, 0, False) + if ok and device: + self.ID = int(re.findall(r'- (\d+)', device)[0]) + if firstRun: return + else: + self.Q1.put(1), self.Q1.put(1) # Trip breaker to stop data collection + self.T1.join() + self.PA.terminate() # Sacrifice PortAudio for a new device + self.PA = getPA() # Initialize PortAudio + self.startStream() + + # Updates objects during loop + def updateObjects(self): + if self.checkLateNum: + self.showLateFrames(1) + if self.checkBarNum: + self.showBarNum(1) + if self.checkLatency: + self.showLatency(1) + if self.checkRainbow: + self.setRainbow(1) + + # Sets the text color to better see it + def setTextPalette(self): + if self.backgroundColor.value() <= 128: + self.textPalette.setColor(QPalette.WindowText, QColor(255, 255, 255)) + else: + self.textPalette.setColor(QPalette.WindowText, QColor(0, 0, 0)) + + self.setPalette(self.textPalette) + + # Creates the bars for painter to draw + def prePaint(self): + # Get audio data + while len(self.dataList) < self.audioFrames: + self.dataList = self.Q1.get() + if self.split: + self.rightDataList = self.Q2.get() + + # Process audio data + oldBarValues = self.barValues + self.barValues = revidiaLoop(self.dataList, self.widthSize, self.audioFrames) + oldSplitValues = self.splitBarValues + if self.split: + self.splitBarValues = revidiaLoop(self.rightDataList, self.widthSize, self.audioFrames) + + # Smooth audio data using past averages + if self.interp: + if not hasattr(self, 'oldList'): + self.oldList = [] + self.oldSplitList = [] + if len(self.oldList) <= self.interp: + self.oldList.append(list(oldBarValues)) + if self.split: + self.oldSplitList.append(list(oldSplitValues)) + while len(self.oldList) > self.interp: + del (self.oldList[0]) + if self.split: + while len(self.oldSplitList) > self.interp: + del (self.oldSplitList[0]) + + self.barValues = interpData(self.barValues, self.oldList) + if self.split: + self.splitBarValues = interpData(self.splitBarValues, self.oldSplitList) + + # Setup painter object + def paintEvent(self, event): + if not hasattr(self, 'barValues'): + return + if self.split: + if not hasattr(self, 'splitBarValues'): + return + painter = QPainter(self) + painter.setPen(QPen(Qt.NoPen)) # Removes pen + self.paintBackground(event, painter) + if not self.cutout: + self.paintBars(event, painter) + if self.checkNotes: + self.paintNotes(event, painter) + if self.checkDB: + self.paintDB(event, painter) + painter.end() + + # Draw background + def paintBackground(self, event, painter): + painter.setBrush(self.backgroundColor) + if not self.cutout: # Normal background + painter.drawRect(0, 0, self.size().width(), self.size().height()) + else: # Cutout background + xSize = self.barWidth + xPos = (self.gapWidth // 2) + yPos = 0 + for y in range(len(self.barValues)): + ySizeV = int(self.barValues[y] * self.barHeight) + ySize = self.size().height() - ySizeV + painter.drawRect(xPos - self.gapWidth, yPos, self.gapWidth, self.size().height()) # Gap bar + + if self.split and self.splitBarValues != [0]: + ySplitV = int(self.splitBarValues[y] * self.barHeight) + ySize = (self.size().height() // 2) - ySizeV + painter.drawRect(xPos, yPos, xSize, ySize) # Top background + ySize = (self.size().height() // 2) - ySplitV + painter.drawRect(xPos, self.size().height(), xSize, -ySize) # bottom background + else: + painter.drawRect(xPos, yPos, xSize, ySize) + + xPos += self.wholeWidth + + painter.drawRect(xPos - self.wholeWidth + xSize, yPos, + self.size().width() + self.wholeWidth - xPos, self.size().height()) # Last Gap bar + + # Draw bars + def paintBars(self, event, painter): + ySizeList = [] + yPosList = [] + xSize = self.barWidth + xPos = (self.gapWidth // 2) + yPos = self.size().height() + viewHeight = self.size().height() + painter.setBrush(self.barColor) # Fill of bar color + + for y in range(len(self.barValues)): + ySize = int(self.barValues[y] * self.barHeight) + + if self.split and self.splitBarValues != [0]: + if ySize > viewHeight // 2: + ySize = viewHeight // 2 + ySplitV = int(self.splitBarValues[y] * self.barHeight) + if ySplitV > viewHeight // 2: + ySplitV = viewHeight // 2 + yPos = (self.size().height() // 2) + ySplitV + ySize = ySplitV + ySize + + if ySize > viewHeight: + ySize = viewHeight + if not self.outline: + painter.drawRect(xPos, yPos, xSize, -ySize) + + xPos += self.wholeWidth + if self.outlineThick: + ySizeList.append(ySize) + yPosList.append(yPos) + + if self.outlineThick: + xPos = (self.gapWidth // 2) + + painter.setBrush(self.outlineColor) # Fill of outline color + for y in range(len(self.barValues)): + ySize = ySizeList[y] + yPos = yPosList[y] + if ySize > 0: # Hack way of making outline without the (Slow QPen) + painter.drawRect(xPos, yPos, self.outlineThick, -ySize) # Left + painter.drawRect(xPos, yPos-ySize, self.barWidth, self.outlineThick) # Top + painter.drawRect(xPos + self.barWidth, yPos-ySize, -self.outlineThick, ySize) # Right + painter.drawRect(xPos, yPos, self.barWidth, -self.outlineThick) # Bottom + + xPos += self.wholeWidth + + # Draws notes being guessed on by plot frequency + def paintNotes(self, event, painter): + pen = QPen() + if self.barColor.value() <= 128: + pen.setColor(QColor(255, 255, 255)) + else: + pen.setColor(QColor(0, 0, 0)) + painter.setPen(pen) + font = QFont() + fontSize = self.barWidth - (self.outlineThick * 2) - 1 + if fontSize < 1: fontSize = 1 + font.setPixelSize(fontSize) + painter.setFont(font) + + notes = plotNotes(self.audioFrames, self.sampleRate, self.widthSize) + xSize = self.barWidth + 1 + ySize = xSize + 5 + xPos = self.gapWidth // 2 + yPos = self.size().height() - self.barWidth - 5 + for note in notes: + painter.drawText(xPos, yPos, xSize, ySize, Qt.AlignHCenter, note) + xPos += self.wholeWidth + + # Draws a dB bar in right corner + def paintDB(self, event, painter): + if len(self.dataList) >= self.audioFrames: + dbValue = getDB(self.dataList) + + painter.setPen(self.textPalette.color(QPalette.WindowText)) + painter.setFont(QApplication.font()) + if dbValue > -1.0: + painter.setPen(QColor(255, 30, 30)) + xPos = self.size().width() - 35 + yPos = 145 + + painter.drawText(xPos, yPos, str(dbValue)) + + if dbValue == -float('Inf'): + return + xPos = self.size().width() - 15 + yPos = yPos - 15 + ySize = (-int(dbValue) - 50) * 2 + if ySize > 0: + return + gradient = QLinearGradient(xPos, yPos-35, xPos, yPos-100) # xStart, yStart, xStop, yStop + gradient.setColorAt(0, QColor(50, 255, 50)) + gradient.setColorAt(0.5, QColor(255, 200, 0)) + gradient.setColorAt(1, QColor(255, 50, 50)) + painter.setBrush(gradient) + + painter.drawRect(xPos, yPos, 5, ySize) + + # Toggle for audio split setting + def setSplit(self, on): + if on: + self.split = 1 + else: + self.split = 0 + + self.Q1.put(1), self.Q1.put(1) # Trip breaker to stop data collection + self.T1.join() + self.startVidia() + + # Sets amount of Frames Per Sec selected + def setFPS(self, value): + self.frameRate = value + + # Sets amount of interpolation selected + def setInterp(self, index): + self.interp = index + + for f in self.interpDict: + if int(f) != index: + self.interpDict[str(f)].setChecked(False) + + # Sets amount of audio frames selected + def setFrames(self, index): + self.audioFrames = index + + for f in self.audioFramesDict: + if int(f) != index: + self.audioFramesDict[str(f)].setChecked(False) + self.Q1.put(1), self.Q1.put(1) # Trip breaker to stop data collection + self.T1.join() + self.startVidia() + + # Bar color selection + def setBarColor(self): + self.barColor = QColorDialog.getColor(self.barColor,None,None,QColorDialog.ShowAlphaChannel) + + # Background color selection + def setBackgroundColor(self): + self.backgroundColor = QColorDialog.getColor(self.backgroundColor,None,None,QColorDialog.ShowAlphaChannel) + self.setTextPalette() + + # Outline color selection + def setOutlineColor(self): + self.outlineColor = QColorDialog.getColor(self.outlineColor, None, None, QColorDialog.ShowAlphaChannel) + + # Creates a rainbow effect with the bars color + def setRainbow(self, on): + if not self.checkRainbow: + self.rainbowHue = self.barColor.hue() + self.checkRainbow = 1 + if self.barColor.saturation() == 0: + self.barColor.setHsv(0,255,self.barColor.value()) + if on: + if self.rainbowHue < 359: + self.rainbowHue += 1 + else: + self.rainbowHue = 0 + + self.barColor.setHsv(self.rainbowHue,self.barColor.saturation(), + self.barColor.value(),self.barColor.alpha()) + if self.outline: + self.outlineColor.setHsv(self.rainbowHue, self.barColor.saturation(), + self.barColor.value(), self.barColor.alpha()) + else: + self.checkRainbow = 0 + + # Shows sliders for all of bars dimensions + def showBarSize(self, on): + if on: + self.showBarText = QLabel(self) + self.showBarText.setText('Bar Width') + self.showBarText.setGeometry(5, 25, 75, 20) + self.showBarText.show() + self.showBarWidth = QSlider(Qt.Horizontal, self) + self.showBarWidth.setMinimum(1) + self.showBarWidth.setValue(self.barWidth) + self.showBarWidth.setGeometry(0, 45, 85, 20) + self.showBarWidth.valueChanged[int].connect(self.setBarSize) + self.showBarWidth.show() + self.showBarInt() + + self.showGapText = QLabel(self) + self.showGapText.setText('Gap Width') + self.showGapText.setGeometry(105, 25, 75, 20) + self.showGapText.show() + self.showGapWidth = QSlider(Qt.Horizontal, self) + self.showGapWidth.setValue(self.gapWidth) + self.showGapWidth.setGeometry(100, 45, 85, 20) + self.showGapWidth.valueChanged[int].connect(self.setGapSize) + self.showGapWidth.show() + self.showGapInt() + + self.showOutlineText = QLabel(self) + self.showOutlineText.setText('Out Width') + self.showOutlineText.setGeometry(205, 25, 75, 20) + self.showOutlineText.show() + self.showOutlineThick = QSlider(Qt.Horizontal, self) + self.showOutlineThick.setMaximum(50) + self.showOutlineThick.setValue(self.outlineThick) + self.showOutlineThick.setGeometry(200, 45, 85, 20) + self.showOutlineThick.valueChanged[int].connect(self.setOutlineSize) + self.showOutlineThick.show() + self.showOutlineInt() + + self.showHeightText = QLabel(self) + self.showHeightText.setText('Height') + self.showHeightText.setGeometry(30, 65, 50, 20) + self.showHeightText.show() + self.showHeightScroll = QSlider(Qt.Vertical, self) + self.showHeightScroll.setMinimum(-100) + self.showHeightScroll.setMaximum(100) + self.showHeightScroll.setValue(0) + self.showHeightScroll.setGeometry(0, 70, 20, 150) + self.showHeightScroll.valueChanged.connect(self.setBarHeight) + self.showHeightScroll.show() + self.showHeightInt() + else: + self.showBarWidth.close() + self.showGapWidth.close() + self.showOutlineThick.close() + self.showHeightScroll.close() + self.showBarText.close() + self.showGapText.close() + self.showOutlineText.close() + self.showHeightText.close() + + self.showBarTextInt.close() + self.showGapTextInt.close() + self.showOutlineTextInt.close() + self.showHeightTextInt.close() + + # Sets bar width selected + def setBarSize(self, value): + self.barWidth = value + self.wholeWidth = self.barWidth + self.gapWidth + if self.outlineThick > self.barWidth // 2: + self.setOutlineSize(self.outlineThick) + self.showBarInt() + + # Show bar current int + def showBarInt(self): + if hasattr(self, 'showBarTextInt'): + self.showBarTextInt.close() + + self.showBarTextInt = QLabel(self) + self.showBarTextInt.setText(str(self.barWidth)) + self.showBarTextInt.setGeometry(70, 25, 100, 20) + self.showBarTextInt.show() + + # Sets gap between bars selected + def setGapSize(self, value): + self.gapWidth = value + self.wholeWidth = self.barWidth + self.gapWidth + self.showGapInt() + + # Show gap current int + def showGapInt(self): + if hasattr(self, 'showGapTextInt'): + self.showGapTextInt.close() + + self.showGapTextInt = QLabel(self) + self.showGapTextInt.setText(str(self.gapWidth)) + self.showGapTextInt.setGeometry(170, 25, 100, 20) + self.showGapTextInt.show() + + # Sets outline size selected + def setOutlineSize(self, value): + if value <= self.barWidth // 2: + self.outlineThick = value + else: + self.outlineThick = self.barWidth // 2 + self.showOutlineThick.setValue(self.outlineThick) + self.showOutlineInt() + + # Show outline current int + def showOutlineInt(self): + if hasattr(self, 'showOutlineTextInt'): + self.showOutlineTextInt.close() + + self.showOutlineTextInt = QLabel(self) + self.showOutlineTextInt.setText(str(self.outlineThick)) + self.showOutlineTextInt.setGeometry(275, 25, 100, 20) + self.showOutlineTextInt.show() + + # Sets bar height selected + def setBarHeight(self): + while self.showHeightScroll.isSliderDown(): + QApplication.processEvents() # This is NEEDED to stop gui freezing + self.repaint() + + value = self.showHeightScroll.value() + if value > 0: + value = 1 + value / 10000 + if self.barHeight < 10: + self.barHeight *= value + elif value < 0: + value = 1 + -value / 10000 + if self.barHeight > 0.000001: + self.barHeight /= value + + self.showHeightInt() + self.showHeightScroll.setValue(0) + + # Show height current int + def showHeightInt(self): + if hasattr(self, 'showHeightTextInt'): + self.showHeightTextInt.close() + + self.showHeightTextInt = QLabel(self) + self.showHeightTextInt.setText(str(round(self.barHeight * 1000, 2))) + self.showHeightTextInt.setGeometry(30, 85, 45, 20) + self.showHeightTextInt.show() + + # Sets bars outline only + def setOutline(self, on): + if on: + self.outline = 1 + else: + self.outline = 0 + + # Sets bars to cut out background instead of drawn + def setCutout(self, on): + if on: + self.cutout = 1 + else: + self.cutout = 0 + + # Show estimation of where notes are + def showNotes(self, on): + if on: + self.checkNotes = 1 + else: + self.checkNotes = 0 + + # Show how many frames fail to meet frame time set + def showLateFrames(self, on): + if self.checkLateNum: + self.showLateNumText.close() + else: + self.showLateNumText = QLabel(self) + self.lateFrames = 0 + if on: + self.checkLateNum = 1 + + self.showLateNumText.setText(str(self.lateFrames) + ' Late') + digits = 0 + number = self.lateFrames + if number == 0: + digits = 1 + while number > 0: + number //= 10 + digits += 1 + lateNumPos = (self.size().width()//2) - 80 - (digits * 8) + self.showLateNumText.setGeometry(lateNumPos, 15, 100, 30) + self.showLateNumText.show() + else: + self.checkLateNum = 0 + self.showLateNumText.close() + + # Shows the amount of bars on screen + def showBarNum(self, on): + if self.checkBarNum: + self.showBarNumText.close() + else: + self.showBarNumText = QLabel(self) + if on: + self.checkBarNum = 1 + + if self.widthSize > (self.audioFrames // 4): + self.widthSize = self.audioFrames // 4 + self.showBarNumText.setText(str(self.widthSize) + ' Bars') + barNumPos = (self.size().width()//2) - 50 + self.showBarNumText.setGeometry(barNumPos, 15, 75, 30) + self.showBarNumText.setAlignment(Qt.AlignCenter) + self.showBarNumText.show() + else: + self.checkBarNum = 0 + self.showBarNumText.close() + + # Shows latency in milliseconds between the audio and bars being drawn + def showLatency(self, on): + if self.checkLatency: + self.showLatencyNum.close() + else: + self.showLatencyNum = QLabel(self) + if on: + self.checkLatency = 1 + + self.showLatencyNum.setText(str(self.latency) + ' ms') + latPos = (self.size().width()//2) + 30 + self.showLatencyNum.setGeometry(latPos, 15, 100, 30) + self.showLatencyNum.show() + else: + self.checkLatency = 0 + self.showLatencyNum.close() + + # Toggle for showing dB bar + def showDB(self, on): + if on: + self.checkDB = 1 + else: + self.checkDB = 0 + + # Adds keyboard inputs + def keyPressEvent(self, event): + if event.key() == Qt.Key_Escape: + self.close() + if event.key() == Qt.Key_Shift: + if not hasattr(self, 'menuToggle'): + self.menuToggle = 0 + + if self.menuToggle == 0: + self.menuToggle = 1 + self.menuBar().hide() + self.fpsDock.hide() + return + if self.menuToggle == 1: + self.menuToggle = 2 + oldY = self.y() + self.setWindowFlags(Qt.FramelessWindowHint) + self.show() + return + if self.menuToggle == 2: + self.menuToggle = 0 + oldY = self.y() + self.setWindowFlags(self.windowFlags() & ~Qt.FramelessWindowHint) + self.menuBar().show() + self.fpsDock.show() + self.show() + self.move(self.x() - 8, oldY - 31) # Fixes a weird bug with the window + return + + # Allows to adjust bars height by scrolling on window + def wheelEvent(self, event): + mouseDir = event.angleDelta().y() + if mouseDir > 0: + if self.barHeight < 10: + self.barHeight *= 1.5 + elif mouseDir < 0: + if self.barHeight > 0.000001: + self.barHeight /= 1.5 + + # Makes sure the window isn't running in the background after closing + def closeEvent(self, event): + sys.exit() + + +# Main class to run program +class RunUI: + app = QApplication(sys.argv) + ex = ReVidiaMain() + sys.exit(app.exec_()) diff --git a/ReVidiaQT_win.py b/ReVidiaQT_win.py new file mode 100644 index 0000000..20aa953 --- /dev/null +++ b/ReVidiaQT_win.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- + +import pyaudio +import struct +import numpy as np + + +def getPA(): # PortAudio + return pyaudio.PyAudio() + + +# Displays device ID options +def deviceName(p): + deviceList = [] + numDevices = p.get_device_count() + for ID in range(numDevices): + if (p.get_device_info_by_index(ID).get('maxInputChannels')) > 0: + API = p.get_device_info_by_index(ID).get('hostApi') # Removes crap windows duplicate APIs + if not p.get_host_api_info_by_index(API).get('name') in ['Windows WDM-KS', 'Windows DirectSound']: + deviceList.append(p.get_device_info_by_index(ID).get('name') + " - " + str(ID)) + return deviceList + + +# Returns sample rate +def sampleRate(p, device): + return int(p.get_device_info_by_index(device).get('defaultSampleRate')) + + +# Open stream to collect audio data. +def startStream(p, device, samples): + stream = p.open( + input_device_index=device, + format=pyaudio.paInt16, + channels=2, + rate=samples, + input=True, + frames_per_buffer=1) + return stream + + +# Collects the raw audio data and coverts to ints +def audioData(q1, q2, stream, frames, split): + monoList, leftList, rightList = [], [], [] + while True: + data = stream.read(1, exception_on_overflow=False) + if not split: + monoList.append(sum(struct.unpack("2h", data)) // 2) + while len(monoList) > frames: + del (monoList[0]) + if q1.empty(): + q1.put(monoList) + else: + leftList.append(sum(struct.unpack("2h", data)[:1])) # Left + rightList.append(sum(struct.unpack("2h", data)[1:])) # Right + while len(leftList) > frames: + del (leftList[0]) + del (rightList[0]) + if q1.empty() or q2.empty(): + q1.put(leftList) + q2.put(rightList) + + if q1.qsize() > 1: + break + + +# Assigns notes locations based on plots +def plotNotes(frames, samples, width): + plot = samples / frames + startGrow = int(width // 1.5) + expoGrow = ((samples // 4) / (startGrow * plot)) ** (1 / (width - startGrow)) + noteList = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B'] * 9 + notesFreq = [] + for i in range(-8, 100): # C-(-1) - B-8 + notesFreq.append((2 ** (1 / 12)) ** (i - 49) * 440) + + notePlot = [] + noteNum = 0 + low = 0 + for f in range(width): + for n in range(low, 108): + if noteNum - notesFreq[n] < 0: + index = notesFreq.index(min(notesFreq[n], notesFreq[n-1], key=lambda x: abs(x - noteNum))) + notePlot.append(noteList[index]) + low = n + break + + if f < startGrow - 1: + noteNum += plot + else: + noteNum *= expoGrow + + return notePlot + + +# Calculates decibel +def getDB(data): + amp = max(data) / 32767 + if amp < 0: + amp *= -1 + dB = round(20 * np.log10(amp), 1) + + return dB + + +# Interpolates the data to smooth out the bars +def interpData(barValues, oldList): + interpBarValues = barValues + + if len(oldList[0]) == len(barValues): + for oldValues in oldList: + barValues = list(map(lambda new, old: new + old, barValues, oldValues)) + divide = len(oldList) + 1 + interpBarValues = list(map(lambda bars: bars // divide, barValues)) + + return interpBarValues + + +# Processes the audio data into proper +def revidiaLoop(dataList, width, frames): + + if width < 2: width = 2 # Prevents crash when window gets too small + if width > (frames//4): width = (frames//4) # Prevents crash when width is too high + + # The heart of ReVidia, the fourier transform. + transform = np.fft.fft(dataList, frames) + absTransform = np.abs(transform[0:frames//2]) # Each plot is rate/frames = frequency + + # Most of the math to plot ReVidia. + # It scales linearly for 2/3, then exponentially to ~12khz. + startGrow = int(width // 1.5) + expoGrow = ((frames//4)/startGrow)**(1/(width - startGrow)) + xBarMaxFloat = startGrow + xBarMax = startGrow + + barValues = list(map(int, absTransform[0:startGrow])) # Linearly scale + + for z in range(width - startGrow): # Exponentially scale + xBarMin = xBarMax + xBarMaxFloat *= expoGrow + xBarMax = int(-(-xBarMaxFloat // 1)) + if xBarMax - xBarMin <= 0: + xBarMax = xBarMin + 1 + + barValues.append(int(max(absTransform[xBarMin:xBarMax]))) + + # Curvy + # if True: + # xList = [x + 1 for x in range(len(barValues))] + # barValues = np.polyfit(xList, barValues, width // 4) + # barValues = np.poly1d(barValues)(xList) + + return barValues diff --git a/WinReadMe.txt b/WinReadMe.txt new file mode 100644 index 0000000..071e03d --- /dev/null +++ b/WinReadMe.txt @@ -0,0 +1,90 @@ +ReVidia [Windows Port] + +Installation +============================ + +ReVidia on Windows REQUIRES Python 3.7+ +Download from here: +https://www.python.org/downloads/ + +During installation MAKE SURE you check the box: +"Add Python to PATH" +It will not work without it. + +With Python installed, run "install.bat" to install. +It might take a couple minutes, so wait until it says "Done" + +Then simply use the desktop shortcut to enjoy! + + +I also HIGHLY recommend installing VB-CABLE: +https://www.vb-audio.com/Cable/index.htm#DownloadCable + +As this software ONLY accepts input(mics) audio streams for now. + +Without it you might not be able to select speaker's audio +or even start the program at ALL because it also uses +Windows audio APIs that meld better with PortAudio. + +Using VB-CABLE: +After installing VB-CABLE go into the sound control panel +and set "CABLE Input" as your default playback device. +Then in "CABLE Output" Properties check the box +"Listen to this device" to your speakers you normally use. + +Then, if you like to use your speaker's audio +just select any "CABLE Output" device in ReVidia. + + +Usage/About +============================ + +If you start the program and no device list is shown, it means that +no API you have is compatible and you should install VB-CABLE shown above. + +Because how Windows uses transparency: +If you like to make the background disappear go into Design|Color|Background Color +and change the alpha channel to 0. Then press SHIFT twice. +Press shift once more while on the window to make it reappear. + +The icon is just my profile pic as a place holder until I come with a better custom icon. + +Settings: + +For best performance leave as default, shrink the program size and lower the FPS as needed. +Use Stats's Late Frames and Latency to gauge performance. + +On a scale from [0] being negligible/none to [5] being the worst performance cost of settings: + +Main|Split Audio - Splits the audio into left(top half) and right(bottom half) channels [5] +Main|Interpolation - By averaging old data with new data it makes the bars appear smoother [1] +Main|Frames - Audio frames in the buffer, the higher the number the higher the accuracy [5] + +Design|Color|Bar Color/Background Color/Outline Color - Allows to pick color and alpha(transparency) [0-2]* +Design|Color|Rainbow - Creates a rainbow effect to the colors of Bars/(Outline Only) based on current Bar Color [1] +Design|Dimensions - Brings up a scroll bars the change dimensions and look of the bars. [0-5]** +Design|Outline Only - If Out Width is more than 0 it will only show the outline. [0] +Design|Cutout - Instead of drawing the bars it will "cut" the background instead + +Stats|Notes - Places notes closest to give plot/bar(sample rate / frames) [4] +Stats|Late Frames - Tells you how many video frames aren't meeting the frame time(FPS) deadline [0-1] +Stats|Bars - Tells how many bars are being drawn in the window [0-1] +Stats|Latency - Tells the time in ms(milliseconds) to complete drawing a frame [0-1] +Stats|dB Bar - Using a bar it shows you the loudness of the audio in decibels [1] + +* If you change the alpha anything less than 255 will cost some performance. + +**If you change the width or gap to allow more bars (use Stats|Bars to check) will decrease performance. + Having (Out)line more than 0/enabled will decrease performance. + And increasing the height to allow the bars to take up more screen space will decrease performance. + +Shortcuts: + +Scroll - Change Bar height on the fly +Shift - Press once to hide toolbar, Once more to hide the borders and then on more time to bring up the borders and the toolbar again. +Esc - Close program + + +Tips: + +- Lower the Main|Interpolation for more responsiveness, this helps when you have a low FPS set. diff --git a/install.bat b/install.bat new file mode 100644 index 0000000..9f8fb3a --- /dev/null +++ b/install.bat @@ -0,0 +1,22 @@ +:: Create venv required +python -m venv venv +call %cd%\venv\Scripts\activate +pip install -r %cd%\venv\requirements.txt + +:: Create desktop shortcut +set SCRIPT="%TEMP%\%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%.vbs" + +echo Set oWS = WScript.CreateObject("WScript.Shell") >> %SCRIPT% +echo sLinkFile = "%CD%\ReVidia.lnk" >> %SCRIPT% +echo Set oLink = oWS.CreateShortcut(sLinkFile) >> %SCRIPT% +echo oLink.IconLocation = "%CD%\docs\REV.ico" >> %SCRIPT% +echo oLink.TargetPath = "%CD%\RevidiaGUI_win.pyw" >> %SCRIPT% +echo oLink.Description = "Audio Visualizer" >> %SCRIPT% + +echo oLink.Save >> %SCRIPT% +cscript /nologo %SCRIPT% +del %SCRIPT% + +echo. +echo Done +timeout /t -1