Created
November 10, 2024 18:13
-
-
Save ancientstraits/0c220d40f9868e294388f1b8ba26ed13 to your computer and use it in GitHub Desktop.
Keyboard sampler python script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import wave | |
from pathlib import Path | |
import json | |
import time | |
import sys | |
import keyboard | |
import pyaudio | |
writing = False | |
reverse = False | |
reverse_key = {} | |
paused = False | |
pause_time = 0.0 | |
poses = {} | |
is_pressed = {} | |
def n_samples_to_time(n_samples, sample_rate): | |
total_secs = n_samples / sample_rate | |
mins = total_secs // 60 | |
secs = total_secs % 60 | |
subsecs = (secs * 100) % 100 | |
return f'{int(mins)}:{int(secs)}.{int(subsecs)} ' | |
def pressed(key): | |
if keyboard.is_pressed(key): | |
if key in is_pressed and is_pressed[key]: | |
return False | |
else: | |
# print(key) | |
is_pressed[key] = True | |
return True | |
else: | |
if key in is_pressed and is_pressed[key]: | |
# print('no', key) | |
is_pressed[key] = False | |
return False | |
def num_pressed(): | |
for i in range(10): | |
if pressed(str(i)): | |
return str(i) | |
return False | |
def reverse_audio(audio, sample_size, chunk_size): | |
ret = b'' | |
for i in range(sample_size*chunk_size, 0, -sample_size): | |
ret += audio[i-sample_size:i] | |
return ret | |
CHUNK = 1024 | |
if len(sys.argv) < 3: | |
print(f'Plays a wave file. Usage: {sys.argv[0]} infile.wav outfile.wav') | |
sys.exit(-1) | |
# load positions from save file | |
save_file_path = sys.argv[1] + '.json' | |
if Path(save_file_path).exists(): | |
with open(save_file_path, 'r') as f: | |
arr = json.loads(f.read()) | |
for entry in arr: | |
poses[entry['key']] = entry['offset'] | |
reverse_key[entry['key']] = entry['reverse'] | |
out = wave.open(sys.argv[2], 'wb') | |
with wave.open(sys.argv[1], 'rb') as wf: | |
out.setnchannels(wf.getnchannels()) | |
out.setsampwidth(wf.getsampwidth()) | |
out.setframerate(wf.getframerate()) | |
# Instantiate PyAudio and initialize PortAudio system resources (1) | |
p = pyaudio.PyAudio() | |
# Open stream (2) | |
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), | |
channels=wf.getnchannels(), | |
rate=wf.getframerate(), | |
output=True) | |
# Play samples from the wave file (3) | |
while True: | |
print(f'\r{n_samples_to_time(wf.tell(), wf.getframerate())}', end='') | |
if not paused: | |
data = wf.readframes(CHUNK) | |
if not len(data): | |
break | |
if reverse: | |
data = reverse_audio(data, wf.getsampwidth()*wf.getnchannels(), CHUNK) | |
stream.write(data) | |
if writing: | |
out.writeframes(data) | |
if reverse: | |
pos = wf.tell() - 2*CHUNK | |
if pos < 0: | |
reverse = False | |
pos = 0 | |
wf.setpos(pos) | |
if pressed('esc'): | |
break | |
if pressed('space'): | |
if paused and writing: | |
out.writeframes(b'\x00' * wf.getsampwidth() * wf.getnchannels() * round( | |
wf.getframerate() * (time.time() - pause_time) | |
)) | |
else: | |
pause_time = time.time() | |
paused = not paused | |
if pressed('r'): | |
reverse = not reverse | |
if pressed('w'): | |
writing = not writing | |
if writing: | |
print('Writing started') | |
else: | |
print('Writing stopped') | |
seek_mul = 5 if keyboard.is_pressed('shift') else 1 | |
if pressed('a'): | |
pos = wf.tell() - seek_mul*wf.getframerate() | |
if pos < 0: | |
pos = 0 | |
wf.setpos(pos) | |
if pressed('d'): | |
pos = wf.tell() + seek_mul*wf.getframerate() | |
if (pos >= wf.getnframes()): | |
pos = wf.getnframes() - 1 | |
wf.setpos(pos) | |
num = num_pressed() | |
if not num: | |
continue | |
# print('OOOOOOOOOOOOO') | |
if keyboard.is_pressed('ctrl'): | |
pos = wf.tell() | |
print(f'poses[{num}] = {pos}') | |
poses[num] = pos | |
reverse_key[num] = reverse | |
elif (num in poses) and poses[num]: | |
reverse = reverse_key[num] | |
wf.setpos(poses[num]) | |
# Close stream (4) | |
stream.close() | |
# Release PortAudio system resources (5) | |
p.terminate() | |
out.close() | |
json_arr = [] | |
for key in poses: | |
json_arr.append({'key': key, 'offset': poses[key], 'reverse': reverse_key[key]}) | |
with open(save_file_path, 'w') as f: | |
f.write(json.dumps(json_arr)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment