FPGA-RISC-V-CPU / hardware / scripts / audio / piano
piano
Raw
#!/usr/bin/env python3

import os
import serial
import time
from enum import IntEnum

from models.utils import note_map

from tkinter import *
import tkinter as tk
from tkinter import ttk

if __name__ == "__main__":
    if os.name == 'nt':
        print('Windows machine!')
        ser = serial.Serial()
        ser.baudrate = 115200
        ser.port = 'COM11'  # CHANGE THIS COM PORT
        ser.open()
    else:
        print('Not windows machine!')
        ser = serial.Serial('/dev/ttyUSB0')
        ser.baudrate = 115200

    class Command(IntEnum):
        SET_MOD_FCW = 1
        SET_MOD_SHIFT = 2
        START_PLAY = 3
        STOP_PLAY = 4
        SET_SYNTH_SHIFT = 5
        RESET = 6

    def cmd(command: Command, args: bytearray):
        # Write the command code
        ser.write(bytearray([int(command)]))
        time.sleep(0.001)
        # Write the argument from lowest byte to next
        for b in args:
            ser.write(bytearray([b]))
            time.sleep(0.001)
        time.sleep(0.002)

    def fcw_to_bytearray(fcw: int) -> bytearray:
        bytes = []
        for byte_idx in range(3):
            mask = 0xFF << (8 * byte_idx)
            fcw_byte = (fcw & mask) >> (8 * byte_idx)
            bytes.append(fcw_byte)
        return bytearray(bytes)

    def freq_to_fcw(freq: float, fsamp: int = 60000, pa_bits: int = 24) -> int:
        return int(round((freq / fsamp) * 2**pa_bits))

    cmd(Command.RESET, bytearray([]))
    cmd(Command.SET_MOD_SHIFT, bytearray([0]))
    cmd(Command.SET_MOD_FCW, fcw_to_bytearray(freq_to_fcw(0)))

    cmd(Command.START_PLAY, fcw_to_bytearray(freq_to_fcw(440)))
    time.sleep(0.1)
    cmd(Command.START_PLAY, fcw_to_bytearray(freq_to_fcw(220)))
    time.sleep(0.1)
    cmd(Command.START_PLAY, fcw_to_bytearray(freq_to_fcw(110)))
    time.sleep(0.1)
    cmd(Command.STOP_PLAY, fcw_to_bytearray(freq_to_fcw(110)))

    os.system('xset r off')  # see https://stackoverflow.com/questions/27215326/tkinter-keypress-keyrelease-events

    def keyup(e):
        char = chr(e.keysym_num)
        if char in note_map:
            cmd(Command.STOP_PLAY, fcw_to_bytearray(freq_to_fcw(note_map[char])))

    def keydown(e):
        char = chr(e.keysym_num)
        if char in note_map:
            cmd(Command.START_PLAY, fcw_to_bytearray(freq_to_fcw(note_map[char])))

    def change_mod_fcw(val):
        if int(val) == 0:
            freq = 0
        else:
            freq = int(round(10**(int(val) / 1000)))
        cmd(Command.SET_MOD_FCW, fcw_to_bytearray(freq_to_fcw(freq)))
        mod_fcw_freq["text"] = "Modulator Frequency (Hz): {}".format(freq)

    def change_mod_shift(val):
        cmd(Command.SET_MOD_SHIFT, bytearray([int(val)]))

    root = Tk()
    frm = tk.Frame(root)
    frm.grid()
    frm.bind("<KeyPress>", keydown)
    frm.bind("<KeyRelease>", keyup)
    tk.Label(frm, text="FM Synth").grid(column=0, row=0)
    mod_fcw = tk.Scale(frm, from_=0, to=4500, length=1000, tickinterval=1000, label="Modulator Freq (Log scale)", orient=HORIZONTAL, command=change_mod_fcw)
    mod_fcw.grid(column=0, row=1)
    mod_fcw.set(0)

    mod_fcw_freq = tk.Label(frm, text="")
    mod_fcw_freq.grid(column=0, row=2)

    mod_shift = tk.Scale(frm, from_=0, to=10, length=600, tickinterval=1, label="Modulator Shift", orient=HORIZONTAL, command=change_mod_shift)
    mod_shift.grid(column=0, row=3)
    mod_shift.set(0)

    tk.Label(frm, text="Type in this window to play notes").grid(column=0, row=4)

    tk.Button(frm, text="Quit", command=root.destroy).grid(column=0, row=5)
    frm.pack()
    frm.focus_set()
    root.mainloop()

    os.system('xset r on')