Qt Interactive

Example script (scamp): examples/Interactivity & visualization/Responding to a GUI/qt_interactive.py

Download: qt_interactive.py

A draggable PyQt rectangle drives live playback in a server session. Moving the rectangle fires a callback that sets the tempo of the clock running the note loop; its vertical position still sets the pitch.

Topics: Interactivity & visualization › Responding to a GUI

from scamp import *

from PyQt5 import QtCore, QtWidgets


class DraggableRect(QtWidgets.QGraphicsRectItem):
    """Movable rectangle that calls `on_move(x, y)` each time it is dragged."""

    def __init__(self, rect, on_move=None):
        super().__init__(rect)
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
        self.on_move = on_move

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.on_move is not None:
            self.on_move(value.x(), value.y())
        return super().itemChange(change, value)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        scene = QtWidgets.QGraphicsScene(self)
        view = QtWidgets.QGraphicsView(scene)
        scene.setSceneRect(QtCore.QRectF(0, 0, 800, 800))

        self.setCentralWidget(view)

        self.rect_item = DraggableRect(QtCore.QRectF(0, 0, 100, 100))
        scene.addItem(self.rect_item)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.setFixedSize(QtCore.QSize(800, 800))
    w.show()

    # map the rectangle's x position (0–800) to a tempo in BPM
    tempo_response = Envelope([30, 600], [800], [3])

    # leaving the `with` block kills the session (and the forked note loop), so closing
    # the window shuts everything down gracefully rather than leaking the scheduler thread
    with Session().run_as_server() as s:
        piano = s.new_part("piano")
        s.start_transcribing()

        def play_notes():
            while True:
                piano.play_note((1 - w.rect_item.y() / 800) * 40 + 60, 1.0, 1.0)

        play_clock = s.fork(play_notes)

        # whenever the rectangle moves, retune the note loop's tempo from its x position
        w.rect_item.on_move = lambda x, y: setattr(play_clock, "tempo", tempo_response.value_at(x))

        app.exec_()  # blocks until the window is closed

        performance = s.stop_transcribing()

    performance.to_score().show()