Skip to content

Instantly share code, notes, and snippets.

@haseeb-heaven
Created November 24, 2023 23:26
Show Gist options
  • Save haseeb-heaven/3aa86e25a6e3be9ff0b1d8a38f3c22d5 to your computer and use it in GitHub Desktop.
Save haseeb-heaven/3aa86e25a6e3be9ff0b1d8a38f3c22d5 to your computer and use it in GitHub Desktop.
Turn your keyboard to Piano using Python.

PyPiano

This Python program uses several libraries to generate and play sine wave tones based on keyboard input.

Libraries Used

  • io: This is a built-in Python library for handling streams of data. It's included with Python, so you don't need to install it separately.

  • pydub: This is a simple and easy-to-use library to manipulate audio files. It can be installed with pip using pip install pydub. It's used in this code to generate a sine wave tone and play it.

  • pynput: This library allows you to control and monitor input devices. It can be installed with pip using pip install pynput. In this code, it's used to listen for key presses and releases.

  • threading: This is a built-in Python library for running multiple threads of execution in your program. It's included with Python, so you don't need to install it separately. In this code, it's used to play the tone in a separate thread so that the program can continue listening for key presses.

Functionality

The program listens for key presses and releases. When a key is pressed, the program generates a sine wave tone corresponding to a piano note and plays it. The tone is played in a separate thread so that the program can continue listening for key presses.

import io
from pydub import AudioSegment
from pydub.generators import Sine
from pydub.playback import play
from pynput.keyboard import Key, Listener
import threading
from math import log2
# Function to generate a sine wave tone
def generate_tone(frequency, duration=500):
"""
Generate a sine wave tone with the given frequency and duration.
Args:
frequency (float): The frequency of the tone in Hz.
duration (int, optional): The duration of the tone in milliseconds. Defaults to 500.
Returns:
pydub.AudioSegment: The generated tone.
"""
tone = Sine(frequency).to_audio_segment(duration=duration)
return tone
# Function to play the generated tone
def play_tone(tone):
"""
Play the given audio tone.
Args:
tone (pydub.AudioSegment): The audio tone to be played.
"""
try:
play(tone) # play the audio using pydub.playback.play
except Exception as error:
print(f"Error in playing tone: {error}")
# Mapping keyboard keys to piano notes (simplified)
key_to_note = {
'a': 261.63, # C4
'w': 277.18, # C#4/Db4
's': 293.66, # D4
'e': 311.13, # D#4/Eb4
'd': 329.63, # E4
'f': 349.23, # F4
't': 369.99, # F#4/Gb4
'g': 392.00, # G4
'y': 415.30, # G#4/Ab4
'h': 440.00, # A4
'u': 466.16, # A#4/Bb4
'j': 493.88, # B4
'k': 523.25, # C5
'o': 554.37, # C#5/Db5
'l': 587.33, # D5
'p': 622.25, # D#5/Eb5
';': 659.25, # E5
'\'': 698.46, # F5
']': 739.99, # F#5/Gb5
'\\': 783.99, # G5
'z': 830.61, # G#5/Ab5
'x': 880.00, # A5
'c': 932.33, # A#5/Bb5
'v': 987.77, # B5
'b': 1046.50, # C6
}
# Function to handle key press
def on_press(key):
"""
Handle the key press event.
Args:
key (pynput.keyboard.Key): The key that was pressed.
"""
try:
if hasattr(key, 'char') and key.char in key_to_note:
frequency = key_to_note[key.char]
tone = generate_tone(frequency)
threading.Thread(target=play_tone, args=(tone,)).start()
except Exception as error:
print(f"Error in key press handler: {error}")
# Function to handle key release
def on_release(key):
"""
Handle the key release event.
Args:
key (pynput.keyboard.Key): The key that was released.
Returns:
bool: False to stop the listener.
"""
if key == Key.esc:
return False # Stop listener
# Main function to start the keyboard listener
def start_keyboard_piano():
"""
Start the keyboard listener to play piano notes.
"""
with Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
def frequency_to_note(frequency):
"""
Convert a frequency to its corresponding musical note.
Args:
frequency (float): The frequency of the note in Hz.
Returns:
str: The musical note name.
"""
note_names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
octave = 4
index = int(round(12 * log2(frequency / 440.0))) + 49
return note_names[index % 12] + str(octave + index // 12)
if __name__ == "__main__":
try:
print("Key Note")
sharp_keys = [key for key, frequency in key_to_note.items() if '#' in frequency_to_note(frequency)]
natural_keys = [key for key, frequency in key_to_note.items() if '#' not in frequency_to_note(frequency)]
print(' '.join([frequency_to_note(key_to_note[key]) for key in sharp_keys]))
print(' '.join([frequency_to_note(key_to_note[key]) for key in natural_keys]))
start_keyboard_piano()
except Exception as error:
print(f"Error in main function: {error}")
@haseeb-heaven
Copy link
Author

I have windows 11, with admin permissions, I had the same issue creating a similar script using simpleaudio dependency, which means your script might not be the issue. But eventually I settled with winsound which sounds terrible but I didn't need a real piano anyway, I just wanted to create some specific note patterns for another script I'm working on.

I have tested this on MacOS but Python libraries has issues in Windows you need to take extra steps to install them.
Good luck 🤞

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment