Tetris-Music-Player for the VC-20 in Python

Wenn man Python nimmt, kann man sich das erzeugen der Noten fΓΌr einen Music-Player auf dem VC-20 deutlich erleichtern. Mann kann die Noten als Frequenzen einfach generieren, und sich auf den eigentlichen Player und die Noten konzentrieren.

Python-Program: tetris-play.py

  • Das Programm ist im Grunde selbsterklΓ€rend :) kann nach belieben erweitert werden
#!/usr/bin/python
#
#pip install sounddevice numpy

import numpy as np
import sounddevice as sd
import time
import argparse


# notes1 ursprungliches BASIC-Listing, das die begleitenden Arpeggios spielt.
# notes2 die Hauptmelodie (Tetris Theme / Korobeiniki).
# notes3 eine tiefe Basslinie, die die Harmonien unterstutzt.

# Frequenzen fur die wichtigsten Noten
NOTE_FREQUENCIES = {
    "c2": 65.41, "c3": 130.81,
    "d2": 73.42, "d3": 146.83,
    "e2": 82.41, "e3": 164.81,
    "f2": 87.31, "f3": 174.61,
    "g1": 49.00, "g2": 98.00, "g4": 392.00,
    "a1": 55.00, "a2": 110.00, "a4": 440.00, "a5": 880.00,
    "h1": 61.74, "h2": 123.47, "h4": 493.88,
    "c5": 523.25, "d5": 587.33, "e5": 659.25,
    "f5": 698.46, "g5": 783.99,
}

#  Noten jetzt als (note, duration)
notes1 = [
    ("e2",1),("e3",1),("e2",1),("e3",1),("e2",1),("e3",1),("e2",1),("e3",1),
    ("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),
    ("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),
    ("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),
    ("d2",1),("d3",1),("d2",1),("d3",1),("d2",1),("d3",1),("d2",1),("d3",1),
    ("c2",1),("c3",1),("c2",1),("c3",1),("c2",1),("c3",1),("c2",1),("c3",1),
    ("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),
    ("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),
    ("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),
    ("g1",1),("g2",1),("g1",1),("g2",1),("g1",1),("g2",1),("g1",1),("g2",1),
    ("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),
    ("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),
    ("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),
    ("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),
    ("c2",1),("c3",1),("c2",1),("c3",1),("c2",1),("c3",1),("c2",1),("c3",1),
    ("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),
    ("e2",1),("e3",1),("e2",1),("e3",1),("e2",1),("e3",1),("e2",1),("e3",1),
    ("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),
    ("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),
    ("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),
    ("d2",1),("d3",1),("d2",1),("d3",1),("d2",1),("d3",1),("d2",1),("d3",1),
    ("c2",1),("c3",1),("c2",1),("c3",1),("c2",1),("c3",1),("c2",1),("c3",1),
    ("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),("h1",1),("h2",1),
    ("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),("a1",1),("a2",1),
]
notes2 = [
    ("e5",1),("h4",1),("c5",1),("d5",1),("c5",1),("h4",1),
    ("a4",1),("a4",1),("c5",1),("e5",1),("d5",1),("c5",1),
    ("h4",1),("c5",1),("d5",1),("e5",1),
    ("c5",1),("a4",1),("a4",1),("p",2),
    ("p",1),("d5",1),("f5",1),("a5",1),("g5",1),("f5",1),
    ("e5",1),("c5",1),("e5",1),("d5",1),("c5",1),
    ("h4",1),("c5",1),("d5",1),("e5",1),
    ("c5",1),("a4",1),("a4",1),("p",2),
    ("e5",1),("c5",1),
    ("d5",1),("h4",1),
    ("c5",1),("a4",1),
    ("g4",1),
    ("e5",1),("c5",1),
    ("d5",1),("h4",1),
    ("c5",1),("e5",1),("a5",1),("a5",1),
    ("g5",1),
    ("e5",1),("h4",1),("c5",1),("d5",1),("c5",1),("h4",1),
    ("a4",1),("a4",1),("c5",1),("e5",1),("d5",1),("c5",1),
    ("h4",1),("c5",1),("d5",1),("e5",1),
    ("c5",1),("a4",1),("a4",1),("p",2),
    ("p",1),("d5",1),("f5",1),("a5",1),("g5",1),("f5",1),
    ("p",1),("e5",1),("c5",1),("e5",1),("d5",1),("c5",1),
    ("p",1),("h4",1),("c5",1),("d5",1),("e5",1),
    ("p",1),("c5",1),("a4",1),("a4",1),("p",2),
]

# eine einfache, aber passende Basslinie, die sich an den Akkordwechseln der Melodie orientiert 
#(A-Moll D-Moll G-Dur C-Dur)
# ruhig im Vierteltakt, damit sie Fundament gibt, aber nicht nervt.
notes3 = [
    # Teil 1 (Am)
    ("a2",1),("a2",1),("a2",1),("a2",1),
    ("a2",1),("a2",1),("a2",1),("a2",1),

    # Teil 2 (Dm)
    ("d2",1),("d2",1),("d2",1),("d2",1),
    ("d2",1),("d2",1),("d2",1),("d2",1),

    # Teil 3 (G)
    ("g2",1),("g2",1),("g2",1),("g2",1),
    ("g2",1),("g2",1),("g2",1),("g2",1),

    # Teil 4 (C)
    ("c2",1),("c2",1),("c2",1),("c2",1),
    ("c2",1),("c2",1),("c2",1),("c2",1),

    # Wiederholung Am
    ("a2",1),("a2",1),("a2",1),("a2",1),
    ("a2",1),("a2",1),("a2",1),("a2",1),

    # Wiederholung Dm
    ("d2",1),("d2",1),("d2",1),("d2",1),
    ("d2",1),("d2",1),("d2",1),("d2",1),

    # Wiederholung G
    ("g2",1),("g2",1),("g2",1),("g2",1),
    ("g2",1),("g2",1),("g2",1),("g2",1),

    # Wiederholung C
    ("c2",1),("c2",1),("c2",1),("c2",1),
    ("c2",1),("c2",1),("c2",1),("c2",1),
]

def square_wave(frequency, duration, samplerate=44100):
    """Erzeuge Rechteckwelle fur eine Frequenz und Dauer."""
    t = np.linspace(0, duration, int(samplerate * duration), endpoint=False)
    wave = np.sign(np.sin(2 * np.pi * frequency * t))
    return wave

def note_to_wave(note, duration_units, unit_length=0.25, samplerate=44100):
    """Wandelt eine Note in eine Squarewave um."""
    if note == "p":  # Pause
        return np.zeros(int(samplerate * duration_units * unit_length))
    if note not in NOTE_FREQUENCIES:
        return np.zeros(int(samplerate * duration_units * unit_length))
    freq = NOTE_FREQUENCIES[note]
    dur = duration_units * unit_length
    return square_wave(freq, dur, samplerate)


def play_two_voices(notes1, notes2, unit_length=0.25, samplerate=44100):
    """Spielt zwei Stimmen synchronisiert ab."""
    # Lange angleichen (falls eine kurzer ist)
    maxlen = max(len(notes1), len(notes2))
    n1 = notes1 + [("p",1)]*(maxlen-len(notes1))
    n2 = notes2 + [("p",1)]*(maxlen-len(notes2))

    full_song = []
    for (note1, dur1), (note2, dur2) in zip(n1, n2):
        dur = max(dur1, dur2)  # gemeinsame Dauer = langere
        w1 = note_to_wave(note1, dur, unit_length, samplerate)
        w2 = note_to_wave(note2, dur, unit_length, samplerate)
        mix = (w1 + w2) * 0.2  # beide Stimmen summieren, Lautstarke runter
        full_song.append(mix)

    song = np.concatenate(full_song)
    sd.play(song, samplerate=samplerate)
    sd.wait()

def play_three_voices(notes1, notes2, notes3, unit_length=0.25, samplerate=44100):
    maxlen = max(len(notes1), len(notes2), len(notes3))
    n1 = notes1 + [("p",1)]*(maxlen-len(notes1))
    n2 = notes2 + [("p",1)]*(maxlen-len(notes2))
    n3 = notes3 + [("p",1)]*(maxlen-len(notes3))

    full_song = []
    for (note1, dur1), (note2, dur2), (note3, dur3) in zip(n1, n2, n3):
        dur = max(dur1, dur2, dur3)
        w1 = note_to_wave(note1, dur, unit_length, samplerate)
        w2 = note_to_wave(note2, dur, unit_length, samplerate)
        w3 = note_to_wave(note3, dur, unit_length, samplerate)
        mix = (w1 + w2 + w3) * 0.2
        full_song.append(mix)

    song = np.concatenate(full_song)
    sd.play(song, samplerate=samplerate)
    sd.wait()

note_to_vic = {
    # Voice 1 (low)
    "g1": 0xA7, "a1": 0xB0, "h1": 0xB9,
    "c2": 0xBD, "d2": 0xC4, "e2": 0xCA, "f2": 0xCD,
    "g2": 0xD3, "a2": 0xD8, "h2": 0xDC,
    "c3": 0xDE, "d3": 0xE2, "e3": 0xE5, "f3": 0xE6,
    "p":  0x7F,  # Pause

    # Voice 2 (high)
    "c4": 0xDE, "d4": 0xE2, "e4": 0xE5, "f4": 0xE6,
    "g4": 0xEA, "a4": 0xEB, "h4": 0xED,
    "c5": 0xEE, "d5": 0xF0, "e5": 0xF2, "f5": 0xF3,
    "g5": 0xF4, "a5": 0xF5, "h5": 0xF6,
}
def export_hex(notes, note_map):
    out = []
    for note, dur in notes:
        freq = note_map.get(note, 0x7F)  # Default: Pause
        out.append((freq, dur))
    return out

voices = {
    "voice1": notes1,  # Arpeggio-Stimme
    "voice2": notes2,  # Melodie
    "voice3": notes3,  # Bass
}

def export_acme(voice_name, notes, mapping):
    print(voice_name)
    line = "    !byte "
    for i, (note, dur) in enumerate(notes, 1):
        freq = mapping.get(note, 0x7F)
        line += f"${freq:02X}, ${dur:02X}, "
        if i % 8 == 0:
            print(line[:-2])  # letzte ", " entfernen
            line = "    !byte "
    if line.strip() != "!byte":
        print(line[:-2])
    print()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="VC-20 Tetris Player/Exporter")
    parser.add_argument("--play", action="store_true", help="Play the song")
    parser.add_argument("--export", action="store_true", help="Export to ACME format")
    args = parser.parse_args()

    # Default: play, wenn kein Argument angegeben
    if not args.play and not args.export:
        args.play = True

    if args.play:
        play_three_voices(notes1, notes2, notes3)
    if args.export:
        export_acme("voice1", notes1, note_to_vic)
        export_acme("voice2", notes2, note_to_vic)
        export_acme("voice3", notes3, note_to_vic)

Download tetris-play.py



Python-Installation, Anwendung

  • FΓΌ Windows Pyhton hier herunterladen:

https://www.python.org/downloads/windows/

  • Windows 10 …: Python 3.13.7 - Aug. 14, 2025

https://www.python.org/ftp/python/3.13.7/python-3.13.7-amd64.exe

  • Windows 7: Python 3.8.10

https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe

  • Beim Installer Python mit in den Path aufnehmen

  • Es muss sounddevice und numpy einmalig mit pip installiert werden!

 pip install sounddevice numpy

player python1 player python2 player python3

C:\Users\mama>python --version
Python 3.8.10

C:\Users\mama>python tetris-play.py
Traceback (most recent call last):
  File "tetris-play.py", line 5, in <module>
    import numpy as np
ModuleNotFoundError: No module named 'numpy'

C:\Users\mama>pip install sounddevice numpy
Collecting sounddevice
  Downloading sounddevice-0.5.2-py3-none-win_amd64.whl (363 kB)
     |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 363 kB ...
Collecting numpy
  Downloading numpy-1.24.4-cp38-cp38-win_amd64.whl (14.9 MB)
     |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 14.9 MB 371 kB/s
Collecting CFFI>=1.0
  Downloading cffi-1.17.1-cp38-cp38-win_amd64.whl (181 kB)
     |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 181 kB 6.4 MB/s
Collecting pycparser
  Downloading pycparser-2.23-py3-none-any.whl (118 kB)
     |β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 118 kB 6.4 MB/s
Installing collected packages: pycparser, CFFI, sounddevice, numpy
Successfully installed CFFI-1.17.1 numpy-1.24.4 pycparser-2.23 sounddevice-0.5.2

WARNING: You are using pip version 21.1.1; however, version 25.0.1 is available.

You should consider upgrading via the 'c:\users\mama\appdata\local\programs\pyth
on\python38\python.exe -m pip install --upgrade pip' command.

C:\Users\mama>python tetris-play.py

C:\Users\mama>python tetris-play.py --export
voice1
    !byte $CA, $01, $E5, $01, $CA, $01, $E5, $01, $CA, $01, $E5, $01, $CA, $01, $E5, $01
    !byte $B0, $01, $D8, $01, $B0, $01, $D8, $01, $B0, $01, $D8, $01, $B0, $01, $D8, $01
    !byte $B9, $01, $DC, $01, $B9, $01, $DC, $01, $B9, $01, $DC, $01, $B9, $01, $DC, $01
[...]



Und wie spiele ich das ganze jetzt ab?

Artikel erstellt am: 11 September 2025 , aktualisiert am 11 September 2025