documented

This commit is contained in:
tcsenpai 2024-08-18 00:55:30 +02:00
parent 7f5d685120
commit 603c0156f4

98
main.py
View File

@ -11,6 +11,10 @@ pyxel.COLOR_NEON_YELLOW = 10
class NeoRetroSynth: class NeoRetroSynth:
def __init__(self): def __init__(self):
"""
Initialize the NeoRetroSynth.
Sets up the Pyxel environment, initializes variables, and starts the application.
"""
pyxel.init(360, 360, title="NeoRetro Synth - Cyberpunk Edition") pyxel.init(360, 360, title="NeoRetro Synth - Cyberpunk Edition")
self.current_octave = 2 self.current_octave = 2
self.loop_recording = False self.loop_recording = False
@ -56,6 +60,10 @@ class NeoRetroSynth:
pyxel.run(self.update, self.draw) pyxel.run(self.update, self.draw)
def setup_sounds(self): def setup_sounds(self):
"""
Set up the sound bank for the synthesizer.
Configures sounds for different notes, octaves, and drum sounds.
"""
notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
for octave in range(0, 5): # 5 octaves, 0-4 for octave in range(0, 5): # 5 octaves, 0-4
for i, note in enumerate(notes): for i, note in enumerate(notes):
@ -83,6 +91,10 @@ class NeoRetroSynth:
pyxel.sounds[63].set("C#4", "N", "7", "S", 10) # Hi-hat open pyxel.sounds[63].set("C#4", "N", "7", "S", 10) # Hi-hat open
def update(self): def update(self):
"""
Update game state.
Handles user input and updates the synthesizer state each frame.
"""
if pyxel.btnp(pyxel.KEY_Q): if pyxel.btnp(pyxel.KEY_Q):
pyxel.quit() pyxel.quit()
@ -104,6 +116,10 @@ class NeoRetroSynth:
self.toggle_arpeggiator() self.toggle_arpeggiator()
def handle_keyboard_input(self): def handle_keyboard_input(self):
"""
Handle keyboard input for playing notes.
Detects key presses and plays corresponding notes or arpeggiator patterns.
"""
keys = [ keys = [
pyxel.KEY_Z, pyxel.KEY_S, pyxel.KEY_X, pyxel.KEY_D, pyxel.KEY_Z, pyxel.KEY_S, pyxel.KEY_X, pyxel.KEY_D,
pyxel.KEY_C, pyxel.KEY_V, pyxel.KEY_G, pyxel.KEY_B, pyxel.KEY_C, pyxel.KEY_V, pyxel.KEY_G, pyxel.KEY_B,
@ -124,6 +140,10 @@ class NeoRetroSynth:
self.loop.append((0, note, self.current_octave, self.current_waveform)) self.loop.append((0, note, self.current_octave, self.current_waveform))
def play_note(self, note, octave, waveform=None, velocity=100, duration=0.25, channel=0): def play_note(self, note, octave, waveform=None, velocity=100, duration=0.25, channel=0):
"""
Play a single note.
Sets up and plays a note with the specified parameters.
"""
assert channel in [0, 1], f"Invalid channel: {channel}" assert channel in [0, 1], f"Invalid channel: {channel}"
note_name = self.synth_notes[note % 8] note_name = self.synth_notes[note % 8]
if note_name == "C+": if note_name == "C+":
@ -153,6 +173,10 @@ class NeoRetroSynth:
pyxel.play(channel, sound_index, loop=False) pyxel.play(channel, sound_index, loop=False)
def handle_loop_controls(self): def handle_loop_controls(self):
"""
Handle controls for loop recording and playback.
Manages recording, playing, and clearing of loops.
"""
if pyxel.btnp(pyxel.KEY_R): if pyxel.btnp(pyxel.KEY_R):
self.loop_recording = not self.loop_recording self.loop_recording = not self.loop_recording
if not self.loop_recording and self.loop: if not self.loop_recording and self.loop:
@ -171,6 +195,10 @@ class NeoRetroSynth:
pyxel.play(channel, sound) pyxel.play(channel, sound)
def handle_sound_controls(self): def handle_sound_controls(self):
"""
Handle controls for sound parameters.
Manages octave, sound length, waveform, BPM, and volume controls.
"""
if pyxel.btnp(pyxel.KEY_UP) and self.current_octave < 4: if pyxel.btnp(pyxel.KEY_UP) and self.current_octave < 4:
self.current_octave += 1 self.current_octave += 1
if pyxel.btnp(pyxel.KEY_DOWN) and self.current_octave > 1: if pyxel.btnp(pyxel.KEY_DOWN) and self.current_octave > 1:
@ -203,26 +231,48 @@ class NeoRetroSynth:
self.increase_synth_volume() self.increase_synth_volume()
def increase_bpm(self): def increase_bpm(self):
"""
Increase the beats per minute (BPM).
"""
self.bpm = min(self.bpm + 5, 300) # Cap at 300 BPM self.bpm = min(self.bpm + 5, 300) # Cap at 300 BPM
def decrease_bpm(self): def decrease_bpm(self):
"""
Decrease the beats per minute (BPM).
"""
self.bpm = max(self.bpm - 5, 60) # Minimum 60 BPM self.bpm = max(self.bpm - 5, 60) # Minimum 60 BPM
def increase_drum_volume(self): def increase_drum_volume(self):
"""
Increase the drum volume.
"""
self.drum_volume = min(self.drum_volume + 1, 7) self.drum_volume = min(self.drum_volume + 1, 7)
self.setup_sounds() self.setup_sounds()
def decrease_drum_volume(self): def decrease_drum_volume(self):
"""
Decrease the drum volume.
"""
self.drum_volume = max(self.drum_volume - 1, 0) self.drum_volume = max(self.drum_volume - 1, 0)
self.setup_sounds() self.setup_sounds()
def increase_synth_volume(self): def increase_synth_volume(self):
"""
Increase the synth volume.
"""
self.synth_volume = min(self.synth_volume + 1, 7) self.synth_volume = min(self.synth_volume + 1, 7)
def decrease_synth_volume(self): def decrease_synth_volume(self):
"""
Decrease the synth volume.
"""
self.synth_volume = max(self.synth_volume - 1, 0) self.synth_volume = max(self.synth_volume - 1, 0)
def handle_sequencer(self): def handle_sequencer(self):
"""
Handle sequencer playback and editing.
Manages sequencer playback, step progression, and pattern editing.
"""
if pyxel.btnp(pyxel.KEY_TAB): if pyxel.btnp(pyxel.KEY_TAB):
self.sequencer_playing = not self.sequencer_playing self.sequencer_playing = not self.sequencer_playing
self.frame_count = 0 # Reset frame count when toggling sequencer self.frame_count = 0 # Reset frame count when toggling sequencer
@ -302,6 +352,10 @@ class NeoRetroSynth:
self.edit_mode = False self.edit_mode = False
def draw(self): def draw(self):
"""
Draw the user interface.
Renders all visual elements of the synthesizer interface.
"""
pyxel.cls(0) pyxel.cls(0)
self.draw_frame() self.draw_frame()
self.draw_instructions() self.draw_instructions()
@ -312,12 +366,18 @@ class NeoRetroSynth:
self.draw_edit_info() self.draw_edit_info()
def draw_frame(self): def draw_frame(self):
"""
Draw the main frame of the interface.
"""
# Draw a cyberpunk-style frame # Draw a cyberpunk-style frame
pyxel.rectb(0, 0, 360, 360, pyxel.COLOR_NEON_BLUE) pyxel.rectb(0, 0, 360, 360, pyxel.COLOR_NEON_BLUE)
pyxel.rect(1, 1, 358, 10, pyxel.COLOR_NEON_PINK) pyxel.rect(1, 1, 358, 10, pyxel.COLOR_NEON_PINK)
pyxel.text(5, 4, "NeoRetro Synth - Cyberpunk Edition", pyxel.COLOR_BLACK) pyxel.text(5, 4, "NeoRetro Synth - Cyberpunk Edition", pyxel.COLOR_BLACK)
def draw_instructions(self): def draw_instructions(self):
"""
Draw the instruction text for controls.
"""
pyxel.text(10, 20, "NeoRetro Synth Controls:", pyxel.COLOR_NEON_GREEN) pyxel.text(10, 20, "NeoRetro Synth Controls:", pyxel.COLOR_NEON_GREEN)
instructions = [ instructions = [
"Z-M: Synth (C2-C4)", "1-4: Drum sounds", "R: Toggle recording", "Z-M: Synth (C2-C4)", "1-4: Drum sounds", "R: Toggle recording",
@ -332,6 +392,9 @@ class NeoRetroSynth:
pyxel.text(10 + (i // 10) * 180, 30 + (i % 10) * 10, instr, pyxel.COLOR_WHITE) pyxel.text(10 + (i // 10) * 180, 30 + (i % 10) * 10, instr, pyxel.COLOR_WHITE)
def draw_status(self): def draw_status(self):
"""
Draw the current status of the synthesizer.
"""
pyxel.text(10, 140, f"NeoRetro Status:", pyxel.COLOR_NEON_BLUE) pyxel.text(10, 140, f"NeoRetro Status:", pyxel.COLOR_NEON_BLUE)
status = [ status = [
f"Octave: {self.current_octave}", f"Sound Length: {self.sound_length}", f"Octave: {self.current_octave}", f"Sound Length: {self.sound_length}",
@ -344,11 +407,18 @@ class NeoRetroSynth:
pyxel.text(10 + (i // 5) * 180, 150 + (i % 5) * 10, stat, pyxel.COLOR_YELLOW) pyxel.text(10 + (i // 5) * 180, 150 + (i % 5) * 10, stat, pyxel.COLOR_YELLOW)
def draw_volume_info(self): def draw_volume_info(self):
"""
Draw volume information for drums and synth.
"""
pyxel.text(10, 200, f"Drum Volume: {self.drum_volume}", pyxel.COLOR_WHITE) pyxel.text(10, 200, f"Drum Volume: {self.drum_volume}", pyxel.COLOR_WHITE)
pyxel.text(120, 200, f"Synth Volume: {self.synth_volume}", pyxel.COLOR_WHITE) pyxel.text(120, 200, f"Synth Volume: {self.synth_volume}", pyxel.COLOR_WHITE)
pyxel.text(230, 200, f"Sound Length: {self.sound_length}", pyxel.COLOR_WHITE) pyxel.text(230, 200, f"Sound Length: {self.sound_length}", pyxel.COLOR_WHITE)
def draw_sequencer(self): def draw_sequencer(self):
"""
Draw the sequencer interface.
Renders drum and synth patterns, current steps, and edit position.
"""
for i in range(2): # Draw drum sequencers for i in range(2): # Draw drum sequencers
drum_y = 220 + i * 30 drum_y = 220 + i * 30
pyxel.text(10, drum_y, f"Drum {i+1} ({self.drum_lengths[i]}):", pyxel.COLOR_CYAN) pyxel.text(10, drum_y, f"Drum {i+1} ({self.drum_lengths[i]}):", pyxel.COLOR_CYAN)
@ -379,6 +449,9 @@ class NeoRetroSynth:
pyxel.rectb(edit_x, edit_y, 8, 8, pyxel.COLOR_YELLOW) pyxel.rectb(edit_x, edit_y, 8, 8, pyxel.COLOR_YELLOW)
def draw_logo(self): def draw_logo(self):
"""
Draw the NeoRetro Synth logo.
"""
logo = [ logo = [
" _ _ ____ _ ", " _ _ ____ _ ",
" | \ | | ___ ___ | _ \ ___ | |_ _ __ ___ ", " | \ | | ___ ___ | _ \ ___ | |_ _ __ ___ ",
@ -404,6 +477,9 @@ class NeoRetroSynth:
pyxel.text(170, 340 + i * 8, line, pyxel.COLOR_NEON_YELLOW) pyxel.text(170, 340 + i * 8, line, pyxel.COLOR_NEON_YELLOW)
def draw_edit_info(self): def draw_edit_info(self):
"""
Draw information about the current edit mode.
"""
if self.edit_mode: if self.edit_mode:
if self.edit_target < 2: if self.edit_target < 2:
patterns = self.drum_patterns[self.edit_target] patterns = self.drum_patterns[self.edit_target]
@ -422,6 +498,9 @@ class NeoRetroSynth:
pyxel.text(10, 350, "E: Edit mode, T: Switch tracks", pyxel.COLOR_WHITE) pyxel.text(10, 350, "E: Edit mode, T: Switch tracks", pyxel.COLOR_WHITE)
def export_to_midi(self, filename="sequence.mid"): def export_to_midi(self, filename="sequence.mid"):
"""
Export the current sequence to a MIDI file.
"""
def write_var_length(value): def write_var_length(value):
result = bytearray() result = bytearray()
while value: while value:
@ -475,6 +554,9 @@ class NeoRetroSynth:
print(f"MIDI file exported as {filename}") print(f"MIDI file exported as {filename}")
def export_to_wav(self, filename="sequence.wav", duration=2): def export_to_wav(self, filename="sequence.wav", duration=2):
"""
Export the current sequence to a WAV file.
"""
sample_rate = 44100 sample_rate = 44100
t = np.linspace(0, duration, int(sample_rate * duration), False) t = np.linspace(0, duration, int(sample_rate * duration), False)
@ -504,6 +586,9 @@ class NeoRetroSynth:
print(f"WAV file exported as {filename}") print(f"WAV file exported as {filename}")
def save_preset(self, name): def save_preset(self, name):
"""
Save the current synthesizer settings as a preset.
"""
self.presets[name] = { self.presets[name] = {
'waveform': self.current_waveform, 'waveform': self.current_waveform,
'drum_patterns': self.drum_patterns, 'drum_patterns': self.drum_patterns,
@ -512,6 +597,9 @@ class NeoRetroSynth:
print(f"Preset '{name}' saved") print(f"Preset '{name}' saved")
def load_preset(self, name): def load_preset(self, name):
"""
Load a previously saved preset.
"""
if name in self.presets: if name in self.presets:
preset = self.presets[name] preset = self.presets[name]
self.current_waveform = preset['waveform'] self.current_waveform = preset['waveform']
@ -523,15 +611,25 @@ class NeoRetroSynth:
print(f"Preset '{name}' not found") print(f"Preset '{name}' not found")
def toggle_arpeggiator(self): def toggle_arpeggiator(self):
"""
Toggle the arpeggiator on or off.
"""
self.arpeggiator_on = not self.arpeggiator_on self.arpeggiator_on = not self.arpeggiator_on
print(f"Arpeggiator {'ON' if self.arpeggiator_on else 'OFF'}") print(f"Arpeggiator {'ON' if self.arpeggiator_on else 'OFF'}")
def apply_arpeggiator(self, note): def apply_arpeggiator(self, note):
"""
Apply the arpeggiator pattern to a given note.
"""
if self.arpeggiator_on: if self.arpeggiator_on:
return [note + offset for offset in self.arp_pattern] return [note + offset for offset in self.arp_pattern]
return [note] return [note]
def handle_drum_input(self): def handle_drum_input(self):
"""
Handle input for playing drum sounds.
Detects key presses for drum sounds and plays them.
"""
drum_keys = [pyxel.KEY_1, pyxel.KEY_2, pyxel.KEY_3, pyxel.KEY_4] drum_keys = [pyxel.KEY_1, pyxel.KEY_2, pyxel.KEY_3, pyxel.KEY_4]
for i, key in enumerate(drum_keys): for i, key in enumerate(drum_keys):
if pyxel.btnp(key): if pyxel.btnp(key):