Updated original repo to work with Ubuntu 24.04
This commit is contained in:
parent
c7f3237b43
commit
5218c39bb7
21 changed files with 369 additions and 97 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mido
|
||||
threading
|
||||
53
README.md
53
README.md
|
|
@ -2,3 +2,56 @@
|
|||
sample sequencer for raspberry pi
|
||||
|
||||
Video Example here! https://youtu.be/zX5hSGyLj7c
|
||||
|
||||
The sequencer has 4 polyphony and allows the user to store and cue up 6 different sequences
|
||||
that they can alternate between in real time, and supports the ability to change between different samples.
|
||||
|
||||
The Asterisk * and the Period. both operate as function keys.
|
||||
|
||||
MAINFUNCTIONS
|
||||
|
||||
[8] - Toggle Metronome on and off
|
||||
|
||||
[9] - Toggle Recording Mode on and off
|
||||
|
||||
[Enter] - Play/Pause sequence
|
||||
|
||||
[0] - Delete Current Note in Sequence
|
||||
|
||||
[Num Lock and *] - Shut down
|
||||
|
||||
[MIDI Note and .] - Don't quantize to even numbers
|
||||
|
||||
TEMPO FUNCTIONS
|
||||
|
||||
[+] - Course Speed Up BPM
|
||||
|
||||
[-] - Course Slow Down BPM
|
||||
|
||||
[+ and *] - Rapidly Speed Up BPM
|
||||
|
||||
[- and *] - Rapidly Slow Down BPM
|
||||
|
||||
[+ and .] - Fine Speed Up BPM
|
||||
|
||||
[- and .] - Fine Slow Down BPM
|
||||
|
||||
SEQUENCE FUNCTIONS
|
||||
|
||||
[1-6] Recall Sequence 1-6
|
||||
|
||||
[1-6 and .] Store Sequence 1-6
|
||||
|
||||
[0 and .] Clear Current Sequence
|
||||
|
||||
CHANGING SAMPLE FOLDERS
|
||||
|
||||
[1-9 and *] - Change to Sample Pack in Folders 1-9
|
||||
|
||||
[1-9 and * and .] - Change to Sample Pack in Folders 10-18
|
||||
|
||||
SEQUENCE TIPS:
|
||||
|
||||
-Clear the current sequence [0 and .] and store it to any sequences you want cleared out of memory.
|
||||
|
||||
-Recall a sequence and store it to a different number to copy it.
|
||||
|
|
|
|||
79
audiohelp.py
Normal file
79
audiohelp.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import pygame
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
def test_audio():
|
||||
print("=== Pygame Audio System Test ===")
|
||||
print("Initializing Pygame Audio System...")
|
||||
|
||||
# Initialize pygame mixer with specific settings
|
||||
try:
|
||||
pygame.mixer.pre_init(22050, -16, 8, 512)
|
||||
pygame.init()
|
||||
print("✓ Pygame initialized successfully")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to initialize pygame: {e}")
|
||||
return False
|
||||
|
||||
# Check mixer initialization
|
||||
try:
|
||||
print("\nChecking audio configuration...")
|
||||
mixer_settings = pygame.mixer.get_init()
|
||||
if mixer_settings:
|
||||
freq, format, channels = mixer_settings
|
||||
print(f"✓ Sample rate: {freq}Hz")
|
||||
print(f"✓ Format: {format}")
|
||||
print(f"✓ Channels: {channels}")
|
||||
else:
|
||||
print("✗ Mixer not initialized")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to get mixer info: {e}")
|
||||
return False
|
||||
|
||||
# Try to load and play the metronome sound
|
||||
try:
|
||||
print("\nTesting sound playback...")
|
||||
|
||||
if not os.path.exists("metronome.wav"):
|
||||
print("✗ Could not find metronome.wav")
|
||||
return False
|
||||
|
||||
test_sound = pygame.mixer.Sound("metronome.wav")
|
||||
print("✓ Metronome sound loaded successfully")
|
||||
print("\nPlaying metronome sound on loop...")
|
||||
print("Can you hear the sound? (y/n)")
|
||||
|
||||
# Play sound on loop
|
||||
channel = test_sound.play(-1) # -1 means loop indefinitely
|
||||
|
||||
response = input().lower()
|
||||
channel.stop() # Stop the sound
|
||||
|
||||
if response == 'y':
|
||||
print("✓ Audio test successful!")
|
||||
return True
|
||||
else:
|
||||
print("✗ Audio test failed - no sound heard")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to play test sound: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_audio()
|
||||
|
||||
if success:
|
||||
print("\n✓ Audio system is working correctly!")
|
||||
else:
|
||||
print("\n✗ Audio system test failed. Please check your audio configuration.")
|
||||
print("Troubleshooting tips:")
|
||||
print("1. Check if your speakers/headphones are connected and turned on")
|
||||
print("2. Check if system volume is unmuted and turned up")
|
||||
print("3. Verify metronome.wav exists in the same directory as this script")
|
||||
print("4. Try running 'pygame.mixer.quit()' and reinitializing if audio stops working")
|
||||
|
||||
print("\nPress Enter to exit...")
|
||||
input()
|
||||
72
midihelp.py
Normal file → Executable file
72
midihelp.py
Normal file → Executable file
|
|
@ -1,9 +1,69 @@
|
|||
import threading
|
||||
import mido
|
||||
import time
|
||||
import sys
|
||||
|
||||
def test_midi():
|
||||
print("=== MIDI Connection Test ===")
|
||||
|
||||
# Check available ports
|
||||
try:
|
||||
ins = mido.get_input_names()
|
||||
print("\nAvailable MIDI input ports:")
|
||||
for i, port in enumerate(ins):
|
||||
print(f"{i + 1}. {port}")
|
||||
|
||||
if not ins:
|
||||
print("No MIDI input ports found!")
|
||||
return False
|
||||
|
||||
# Look for LPD8
|
||||
lpd8_ports = [port for port in ins if 'LPD8' in port]
|
||||
if lpd8_ports:
|
||||
print("\nFound LPD8 device:", lpd8_ports[0])
|
||||
port_name = lpd8_ports[0]
|
||||
else:
|
||||
print("\nLPD8 not found. Please select a port number:")
|
||||
choice = input("> ")
|
||||
try:
|
||||
port_name = ins[int(choice) - 1]
|
||||
except (ValueError, IndexError):
|
||||
print("Invalid selection!")
|
||||
return False
|
||||
|
||||
# Try to open the port
|
||||
print(f"\nAttempting to connect to: {port_name}")
|
||||
with mido.open_input(port_name) as inport:
|
||||
print("✓ Successfully opened MIDI port")
|
||||
print("\nNow listening for MIDI messages...")
|
||||
print("Press pads/knobs on your controller (Ctrl+C to exit)")
|
||||
|
||||
try:
|
||||
while True:
|
||||
msg = inport.poll()
|
||||
if msg:
|
||||
print(f"Received: {msg}")
|
||||
time.sleep(0.1) # Small delay to prevent CPU hogging
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nTest completed!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError: {e}")
|
||||
print("\nTroubleshooting tips:")
|
||||
print("1. Make sure your MIDI device is properly connected")
|
||||
print("2. Try unplugging and reconnecting the USB cable")
|
||||
print("3. Check if device shows up in your system's MIDI devices")
|
||||
print("4. Ensure no other applications are using the MIDI port")
|
||||
return False
|
||||
|
||||
ins = mido.get_input_names()
|
||||
print(ins)
|
||||
inport = mido.open_input(ins[0])
|
||||
for msg in inport:
|
||||
print(msg)
|
||||
if __name__ == "__main__":
|
||||
success = test_midi()
|
||||
|
||||
if success:
|
||||
print("\n✓ MIDI system is working correctly!")
|
||||
else:
|
||||
print("\n✗ MIDI test failed. Please check your MIDI configuration.")
|
||||
|
||||
print("\nPress Enter to exit...")
|
||||
input()
|
||||
|
|
|
|||
260
sampSeq.py
260
sampSeq.py
|
|
@ -31,18 +31,34 @@ class ThreadedMidi(threading.Thread):
|
|||
threading.Thread.__init__(self, *args, **kwargs)
|
||||
self.daemon = True
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
# get mido going
|
||||
ins = mido.get_input_names()
|
||||
print(ins)
|
||||
inport = mido.open_input(ins[0])
|
||||
for msg in inport:
|
||||
print(msg)
|
||||
if msg.type != "program_change":
|
||||
if msg.type == "note_on":
|
||||
playSound(msg.note)
|
||||
else:
|
||||
toggleSeq()
|
||||
print("Available MIDI inputs:", ins)
|
||||
|
||||
# Look specifically for the LPD8
|
||||
lpd8_ports = [port for port in ins if 'LPD8' in port]
|
||||
if not lpd8_ports:
|
||||
print("Warning: LPD8 not found in MIDI devices")
|
||||
return
|
||||
|
||||
try:
|
||||
print(f"Attempting to connect to {lpd8_ports[0]}")
|
||||
inport = mido.open_input(lpd8_ports[0])
|
||||
print("Successfully connected to LPD8")
|
||||
|
||||
print("Waiting for MIDI messages... (Press pads or knobs on LPD8)")
|
||||
for msg in inport:
|
||||
print(f"Received MIDI message: {msg}")
|
||||
if msg.type != "program_change":
|
||||
if msg.type == "note_on":
|
||||
playSound(msg.note)
|
||||
else:
|
||||
toggleSeq()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in MIDI handling: {e}")
|
||||
|
||||
##FUNCTIONS
|
||||
def playSound(note):
|
||||
|
|
@ -78,12 +94,18 @@ def playSound(note):
|
|||
|
||||
|
||||
def makeNoise(note):
|
||||
j = 0;
|
||||
j = 0
|
||||
while j < len(noteList):
|
||||
if note==noteList[j]:
|
||||
cha[j].play(s[j])
|
||||
break;
|
||||
j = j + 1
|
||||
if note == noteList[j]:
|
||||
try:
|
||||
if s[j] and cha[j]: # Check if sound and channel exist
|
||||
cha[j].play(s[j])
|
||||
else:
|
||||
print(f"Warning: Missing sound or channel for note {note}")
|
||||
except Exception as e:
|
||||
print(f"Error playing note {note}: {e}")
|
||||
break
|
||||
j = j + 1
|
||||
|
||||
def toggleSeq():
|
||||
#Func turns on and off sequence
|
||||
|
|
@ -139,8 +161,18 @@ def setWavs(pathnm):
|
|||
|
||||
|
||||
##INIT PYGAME MIXER
|
||||
pygame.mixer.pre_init(22050, -16, 8, 512)
|
||||
pygame.init()
|
||||
# Initialize with explicit settings and error handling
|
||||
try:
|
||||
pygame.mixer.pre_init(22050, -16, 8, 512)
|
||||
pygame.init()
|
||||
mixer_settings = pygame.mixer.get_init()
|
||||
if mixer_settings:
|
||||
print(f"Audio initialized - Sample rate: {mixer_settings[0]}Hz, Format: {mixer_settings[1]}, Channels: {mixer_settings[2]}")
|
||||
else:
|
||||
print("Warning: Mixer not properly initialized")
|
||||
except Exception as e:
|
||||
print(f"Error initializing audio: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
swing = 0
|
||||
setMidi = False
|
||||
|
|
@ -148,21 +180,27 @@ met = True
|
|||
##GET WAVES FROM CURRENT FOLDER
|
||||
chnWav = False
|
||||
foldNum = 1 #CHANGE TO NUMBER OF SUBFOLDERS
|
||||
paths = [0] * foldNum
|
||||
paths = [] # Initialize as empty list
|
||||
extension = '/*.wav'
|
||||
iya = 0
|
||||
for root, dirs, files in os.walk("."):
|
||||
|
||||
# Change from "." to "samples" directory
|
||||
for root, dirs, files in os.walk("samples"):
|
||||
for dir in dirs:
|
||||
print(dir)
|
||||
paths[iya] = (dir + extension)
|
||||
paths.append(os.path.join("samples", dir + extension)) # Use os.path.join for proper path handling
|
||||
iya = iya + 1
|
||||
|
||||
|
||||
# If no directories found, use samples directory itself
|
||||
if not paths:
|
||||
paths = [os.path.join("samples", extension[1:])] # Remove leading slash from extension
|
||||
|
||||
wavs = glob.glob(paths[0])
|
||||
wavs = sorted(wavs)
|
||||
mets = glob.glob('*.wav')
|
||||
##MIDI NOTE NUMBERS TO PLAY
|
||||
noteList = [36, 38, 40, 41, 43, 45, 47, 48, 50, 52, 53, 55, 57, 59, 60, 62]
|
||||
noteList = [36, 37, 38, 39, 40, 41, 42, 43]
|
||||
# noteList = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
|
||||
##SEQUENCER SIZE
|
||||
seqSize = 32
|
||||
##SEQUENCES
|
||||
|
|
@ -186,91 +224,131 @@ cha = [0] * 16
|
|||
metro = pygame.mixer.Sound(mets[0])
|
||||
|
||||
for i in range(len(wavs)):
|
||||
if i > 15: break
|
||||
if i > 15:
|
||||
break
|
||||
else:
|
||||
print(str(i))
|
||||
if i > 7 :
|
||||
cha[i] = pygame.mixer.Channel(i - 8);
|
||||
else:
|
||||
cha[i] = pygame.mixer.Channel(i);
|
||||
|
||||
s[i] = pygame.mixer.Sound(wavs[i])
|
||||
try:
|
||||
print(f"Loading sound {i}: {wavs[i]}")
|
||||
if i > 7:
|
||||
cha[i] = pygame.mixer.Channel(i - 8)
|
||||
else:
|
||||
cha[i] = pygame.mixer.Channel(i)
|
||||
s[i] = pygame.mixer.Sound(wavs[i])
|
||||
except Exception as e:
|
||||
print(f"Error loading sound {i}: {e}")
|
||||
s[i] = None
|
||||
cha[i] = None
|
||||
|
||||
##START RUNNING MIDI
|
||||
tm = ThreadedMidi(name='Play-Thread')
|
||||
re = True
|
||||
pygame.display.set_mode()
|
||||
screen = pygame.display.set_mode((800, 600), pygame.RESIZABLE)
|
||||
pygame.display.set_caption("Sample Sequencer")
|
||||
|
||||
# Add this helper function at the top with other functions
|
||||
def print_key_press(key_name, action):
|
||||
print(f"Key press: {key_name} - {action}")
|
||||
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_KP_PLUS:
|
||||
if chnWav == True: d.time = d.time + 15
|
||||
else:
|
||||
if re == False: d.time = d.time + 1
|
||||
else: d.time = d.time + 5
|
||||
# Print raw key info for debugging
|
||||
print(f"Raw key event - key: {event.key}, unicode: {event.unicode}")
|
||||
|
||||
# Main Functions
|
||||
if event.key in [pygame.K_KP8, pygame.K_8]:
|
||||
print_key_press("8", "Toggle Metronome")
|
||||
if not chnWav: # Only if not in sample change mode
|
||||
met = not met
|
||||
print(f"Metronome: {'ON' if met else 'OFF'}")
|
||||
|
||||
if event.key == pygame.K_KP_MINUS:
|
||||
if chnWav == True: d.time = d.time - 15
|
||||
else:
|
||||
if re == False: d.time = d.time - 1
|
||||
else: d.time = d.time - 5
|
||||
if event.key == pygame.K_KP_ENTER:
|
||||
elif event.key in [pygame.K_KP9, pygame.K_9]:
|
||||
print_key_press("9", "Toggle Recording Mode")
|
||||
if not chnWav:
|
||||
OVRHT = not OVRHT
|
||||
print(f"Recording Mode: {'ON' if OVRHT else 'OFF'}")
|
||||
|
||||
elif event.key in [pygame.K_KP_ENTER, pygame.K_RETURN]:
|
||||
print_key_press("Enter", "Play/Pause sequence")
|
||||
toggleSeq()
|
||||
d.tick = 31
|
||||
if event.key == pygame.K_KP_MULTIPLY:
|
||||
chnWav = True
|
||||
if event.key == pygame.K_NUMLOCK:
|
||||
if chnWav == True:
|
||||
call("sudo shutdown -h now", shell=True)
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
|
||||
if event.key == pygame.K_KP0:
|
||||
if re == False:
|
||||
seq[0] = [0] * seqSize;
|
||||
seq[1] = [0] * seqSize;
|
||||
seq[2] = [0] * seqSize;
|
||||
seq[3] = [0] * seqSize;
|
||||
else:
|
||||
|
||||
elif event.key in [pygame.K_KP0, pygame.K_0]:
|
||||
print_key_press("0", "Delete Current Note")
|
||||
if re: # When not in store mode
|
||||
delCur = True
|
||||
|
||||
if event.key == pygame.K_KP_PERIOD:
|
||||
# Tempo Functions
|
||||
elif event.key in [pygame.K_KP_PLUS, pygame.K_PLUS, pygame.K_EQUALS]:
|
||||
if chnWav:
|
||||
print_key_press("+ *", "Rapidly Speed Up BPM")
|
||||
d.time = d.time + 15
|
||||
elif not re:
|
||||
print_key_press("+", "Course Speed Up BPM")
|
||||
d.time = d.time + 1
|
||||
else:
|
||||
print_key_press("+ .", "Fine Speed Up BPM")
|
||||
d.time = d.time + 5
|
||||
print(f"New BPM: {d.time}")
|
||||
|
||||
elif event.key in [pygame.K_KP_MINUS, pygame.K_MINUS]:
|
||||
if chnWav:
|
||||
print_key_press("- *", "Rapidly Slow Down BPM")
|
||||
d.time = d.time - 15
|
||||
elif not re:
|
||||
print_key_press("-", "Course Slow Down BPM")
|
||||
d.time = d.time - 1
|
||||
else:
|
||||
print_key_press("- .", "Fine Slow Down BPM")
|
||||
d.time = d.time - 5
|
||||
print(f"New BPM: {d.time}")
|
||||
|
||||
# Function key toggles
|
||||
elif event.key in [pygame.K_KP_MULTIPLY, pygame.K_ASTERISK]:
|
||||
print_key_press("*", "Function key pressed")
|
||||
chnWav = True
|
||||
|
||||
elif event.key in [pygame.K_KP_PERIOD, pygame.K_PERIOD]:
|
||||
print_key_press(".", "Function key pressed")
|
||||
re = False
|
||||
if event.key == pygame.K_KP8:
|
||||
if chnWav == True: setWavs(8)
|
||||
else:
|
||||
if met == True: met = False
|
||||
else: met = True
|
||||
if event.key == pygame.K_KP9:
|
||||
if chnWav == True: setWavs(9)
|
||||
else:
|
||||
if OVRHT == True: OVRHT = False
|
||||
else: OVRHT = True
|
||||
if event.key == pygame.K_KP1:
|
||||
if chnWav == True: setWavs(1)
|
||||
else: recall(1,re)
|
||||
if event.key == pygame.K_KP2:
|
||||
if chnWav == True: setWavs(2)
|
||||
else: recall(2,re)
|
||||
if event.key == pygame.K_KP3:
|
||||
if chnWav == True: setWavs(3)
|
||||
else: recall(3,re)
|
||||
if event.key == pygame.K_KP4:
|
||||
if chnWav == True: setWavs(4)
|
||||
else: recall(4,re)
|
||||
if event.key == pygame.K_KP5:
|
||||
if chnWav == True: setWavs(5)
|
||||
else: recall(5,re)
|
||||
if event.key == pygame.K_KP6:
|
||||
if chnWav == True: setWavs(6)
|
||||
else: recall(6,re)
|
||||
if event.key == pygame.K_KP7:
|
||||
if chnWav == True: setWavs(7)
|
||||
|
||||
|
||||
# Sequence and Sample Pack Functions (1-6 for sequences, 1-9 for sample packs)
|
||||
for num in range(1, 10):
|
||||
if event.key in [getattr(pygame, f'K_KP{num}'), getattr(pygame, f'K_{num}')]:
|
||||
if chnWav:
|
||||
print_key_press(f"{num} *", f"Change to Sample Pack {num}")
|
||||
setWavs(num)
|
||||
elif num <= 6: # Sequence operations only work for 1-6
|
||||
if re:
|
||||
print_key_press(f"{num}", f"Recall Sequence {num}")
|
||||
recall(num, True)
|
||||
else:
|
||||
print_key_press(f"{num} .", f"Store Sequence {num}")
|
||||
recall(num, False)
|
||||
|
||||
# Shutdown combination
|
||||
if event.key == pygame.K_NUMLOCK and chnWav:
|
||||
print_key_press("Num Lock + *", "Shutdown triggered")
|
||||
call("touch numlock.test", shell=True)
|
||||
# call("sudo shutdown -h now", shell=True)
|
||||
# pygame.quit()
|
||||
# sys.exit()
|
||||
|
||||
if event.type == pygame.KEYUP:
|
||||
if event.key == pygame.K_KP_PERIOD:
|
||||
if event.key in [pygame.K_KP_PERIOD, pygame.K_PERIOD]:
|
||||
print_key_press(".", "Function key released")
|
||||
re = True
|
||||
if event.key == pygame.K_KP0:
|
||||
if event.key in [pygame.K_KP0, pygame.K_0]:
|
||||
print_key_press("0", "Delete key released")
|
||||
delCur = False
|
||||
if event.key == pygame.K_KP_MULTIPLY:
|
||||
if event.key in [pygame.K_KP_MULTIPLY, pygame.K_ASTERISK]:
|
||||
print_key_press("*", "Function key released")
|
||||
chnWav = False
|
||||
|
||||
if event.type == pygame.VIDEORESIZE:
|
||||
screen = pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE)
|
||||
print(f"Window resized to {event.w}x{event.h}")
|
||||
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue