Skip to content

Instantly share code, notes, and snippets.

@ancientstraits
Created November 10, 2024 18:13
Show Gist options
  • Save ancientstraits/0c220d40f9868e294388f1b8ba26ed13 to your computer and use it in GitHub Desktop.
Save ancientstraits/0c220d40f9868e294388f1b8ba26ed13 to your computer and use it in GitHub Desktop.
Keyboard sampler python script
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