pyxen.Music
Ein gestreamter Audiotrack, der einem Entity als Komponente angehängt wird.
Music wird bei Bedarf dekodiert und eignet sich für längere Tracks wie Hintergrundmusik und Umgebungsklänge.
Audio-Konzepte findest du unter Audio.
Konstruktor
Music(filename, *, volume=1.0, pan=0.0, loop=False)
filename ist das einzige Positionsargument: der Name eines Musik-Assets im Projekt, ohne Dateiendung. Die anderen drei sind reine Schlüsselwortargumente und setzen den Startwert der gleichnamigen Eigenschaft (siehe Komponenteneigenschaften).
Parameter
| Parameter | Typ | Beschreibung |
|---|---|---|
filename | str | Name des Musik-Assets, ohne Endung |
volume | float | Start-Lautstärke. Standard 1.0 |
pan | float | Start-Stereoposition, -1.0 bis 1.0. Standard 0.0 |
loop | bool | Am Ende des Stücks neu beginnen. Standard False |
Beispiel
theme = Music("theme", volume=0.6, loop=True)
Music anhängen
Beim Spawn:
bgm = world.spawn(music=Music("theme"))
Oder nachträglich:
entity.music = Music("theme")
Das Zuweisen von Music startet die Wiedergabe sofort.
Komponenteneigenschaften
Zugriff über entity.music:
| Eigenschaft | Typ | L/S | Beschreibung |
|---|---|---|---|
loop | bool | L/S | Schleife aktivieren |
volume | float | L/S | Lautstärke (begrenzt >= 0.0) |
pan | float | L/S | Stereo-Panning (-1.0 bis 1.0) |
time | float | L | Aktuelle Wiedergabeposition (Sekunden) |
duration | float | L | Gesamtlänge (Sekunden) |
finished | bool | L | True wenn Wiedergabe abgeschlossen |
channels | listenartig | L | Die Stimmen des Stücks, jede mit eigenem volume — siehe Abschnitt Kanäle |
beat | int | L/S | Schläge seit Wiedergabebeginn. Zuweisen springt an eine Stelle — siehe Musikalische Zeit |
bar | int | L/S | Takte seit Wiedergabebeginn. Zuweisen springt an eine Stelle |
phase | float | L | Wie weit der aktuelle Schlag fortgeschritten ist, 0.0 bis 1.0 |
beats_per_bar | int | L | Schläge pro Takt |
frames_to_beat | int | L | Frames bis zum nächsten Schlag |
tempo | float | L/S | Multiplikator der Wiedergabegeschwindigkeit — siehe Tempo |
bpm | int | L | Schläge pro Minute, wie die Datei sie angibt |
loop
entity.music.loop = True
Wenn aktiviert, startet der Track nach dem Ende automatisch neu. Typisch für Hintergrundmusik.
volume
entity.music.volume = 0.5
Werte unter 0.0 werden auf 0.0 begrenzt.
pan
entity.music.pan = -1.0 # ganz links
entity.music.pan = 1.0 # ganz rechts
entity.music.pan = 0.0 # Mitte
Begrenzt auf den Bereich -1.0 bis 1.0.
time
t = entity.music.time
Nur lesbar. Aktuelle Wiedergabeposition in Sekunden.
duration
d = entity.music.duration
Nur lesbar. Gesamtlänge des Tracks in Sekunden.
finished
if entity.music.finished:
world.destroy(entity)
Nur lesbar. True wenn der Track fertig abgespielt wurde (und loop False ist).
Kanäle
music = entity.music
for i in range(len(music.channels)):
print(i, music.channels[i].volume)
music.channels[1].volume = 0.0 # Stimme 1 stummschalten
Ein Tracker-Modul oder eine MIDI-Datei besteht aus unabhängigen Stimmen, die von einer gemeinsamen Uhr gespielt werden. music.channels liefert pro Stimme ein Handle, und jedes hat:
| Eigenschaft | Typ | L/S | Beschreibung |
|---|---|---|---|
index | int | L | Die Nummer des Kanals |
volume | float | L/S | Lautstärke nur dieser Stimme, begrenzt auf 0.0–1.0 |
Jeder Kanal beginnt bei 1.0. Da alle Stimmen einen gemeinsamen Abspielkopf teilen, gerät keine aus dem Takt, wenn Sie eine leiser oder lauter stellen. So ändern Sie die Intensität eines Soundtracks: eine Stimme dazunehmen, wenn die Action anzieht, und ausblenden, wenn der Spieler stirbt.
Was ein Kanal ist, hängt vom Format ab:
| Format | Kanäle |
|---|---|
| Tracker-Modul | Einer pro Kanal des Moduls (ein typisches .mod hat 4) |
| MIDI | Immer 16, einer pro MIDI-Kanal. Ein Kanal, den die Datei nicht nutzt, ist einfach still |
| OGG | Keine: len(music.channels) ist 0 |
Kanalnummern beginnen bei 0. Ein Kanal hinter dem letzten löst IndexError aus, und music.channels[i] selbst lässt sich nicht zuweisen, nur sein volume. Ein negativer Index zählt vom Ende her, wie bei jeder Python-Liste: music.channels[-1] ist der letzte Kanal.
Bei MIDI schneidet das Setzen eines Kanals auf 0.0 auch Noten dieses Kanals ab, die noch nachklingen.
Musikalische Zeit
if music.beat != last_beat:
last_beat = music.beat
pulse()
| Eigenschaft | Bedeutung |
|---|---|
beat | Ganze Schläge seit Wiedergabebeginn. Er ist monoton: Er zählt durch eine Schleife hindurch weiter, statt auf 0 zurückzufallen, sodass Sie ihn ohne Sonderbehandlung des Umbruchs mit dem Wert des letzten Frames vergleichen können |
bar | Ganze Takte seit Wiedergabebeginn, ebenfalls monoton |
phase | Position innerhalb des aktuellen Schlags, 0.0 bis ausschließlich 1.0. Wo beat in Stufen springt, ist phase kontinuierlich: Nutzen Sie sie für alles, was zwischen den Schlägen ein- oder ausklingen soll |
beats_per_bar | Schläge pro Takt |
frames_to_beat | Frames, bis der nächste Schlag eintrifft, aufgerundet. Damit starten Sie etwas auf dem Schlag statt knapp danach |
Das Zuweisen von beat oder bar springt an die Stelle. music.bar = 8 springt mit einem einzigen Schreibzugriff an den Anfang von Takt 8, alle Kanäle bleiben im Gleichschritt. Im nächsten Frame lesen Sie die neue Position.
Die Zähler messen die Zeit anhand der Datei, deshalb kommt es auf das Format an:
| Format | beat, bar, phase, frames_to_beat, beats_per_bar, bpm |
|---|---|
| Tracker-Modul | Verfügbar. Siehe unten |
| MIDI | Verfügbar, aus der Datei gelesen. Siehe unten |
| OGG | Nicht verfügbar: beat, bar, beats_per_bar, frames_to_beat und bpm lesen -1, phase liest 0.0, und das Zuweisen von beat oder bar bewirkt nichts |
Ein Spiel, das sich zu Musik synchronisiert, die OGG sein kann, sollte daher einmal prüfen, ob music.beat >= 0 gilt, und bei -1 auf einen eigenen Timer zurückfallen.
Schläge in einer MIDI-Datei
Eine MIDI-Datei gibt ihre musikalische Zeit selbst an, und Pyxen liest sie. Ein Schlag ist die Viertelnote der Datei, bpm und die Länge eines Schlags folgen beim Abspielen den Tempowechseln der Datei, und beats_per_bar stammt aus der ersten Taktart der Datei, gezählt in Viertelnoten: 3/4 ergibt 3, 6/8 ebenfalls. Eine Datei ohne Taktangabe steht im Vierertakt.
Wenn ein MIDI-Stück in Schleife läuft, springt die Zählung zum nächsten ganzen Takt: beat und bar zählen weiter aufwärts, und jeder Durchlauf beginnt auf dem ersten Schlag eines Takts. Ein Sprung landet genau auf dem Schlag; eine Note, die schon vor dieser Stelle klang, wird nicht gespielt.
Schläge in einem Tracker-Modul
Ein Modul gibt keine Taktart an, deshalb verwendet Pyxen die eine Definition, die ein Modul mitbringt: sein BPM. Ein Tracker-Tick dauert 2.5 / BPM Sekunden, womit das BPM der Datei genau dann stimmt, wenn ein Schlag 24 Ticks lang ist: vier Zeilen bei Geschwindigkeit 6 oder acht Zeilen bei Geschwindigkeit 3. So zählt Pyxen. Das Zählen von Ticks statt Zeilen hält den Schlag stabil, wenn das Modul die Geschwindigkeit wechselt.
Ein Takt hat vier Schläge. Das ist eine Konvention, keine Angabe aus der Datei: Ein Modul im 3/4-Takt meldet trotzdem beats_per_bar als 4. Ist Ihr Stück mit einer anderen Anzahl Ticks pro Schlag geschrieben, passt der Schlagzähler nicht zu dem, was Sie hören.
Tempo
music.tempo = 1.5 # 50 % schneller, gleiche Tonhöhe
music.tempo = 1.0 # normal
tempo multipliziert die Geschwindigkeit, die das Stück vorgibt. Es wird nichts neu gesampelt, die Tonhöhe bleibt also gleich: Die Stimmen kommen einfach früher oder später. bpm meldet weiterhin den Wert der Datei, egal welches tempo gesetzt ist.
Grenzen: höchstens 8.0. Nach unten geht ein Tracker-Modul nicht unter 0.25, und ein abgelehnter Wert wird ignoriert (die Eigenschaft behält ihren vorherigen Wert); MIDI akzeptiert bis 0.05. tempo hat bei OGG keine Wirkung und liest dort immer 1.0.
beat, phase und frames_to_beat folgen tempo, ein zur Musik synchronisiertes Spiel bleibt also synchron, wenn Sie die Musik beschleunigen.
Ausführliche Beispiele
Ein Puls auf jedem Schlag
Dieses Beispiel setzt ein Tracker-Modul namens theme.mod im Projekt voraus.
from pyxen import *
last_beat = -1
def start():
world.spawn(name="song", music=Music("theme", loop=True))
world.spawn(name="pulse", x=160, y=90,
shape=Circle(24, fill=(1.0, 0.5, 0.2, 1.0)))
def update():
global last_beat
music = world.get(name="song").music
pulse = world.get(name="pulse")
# phase läuft in jedem Schlag von 0 bis 1: groß auf dem Schlag, dann beruhigt es sich.
s = 1.0 + 0.5 * (1.0 - music.phase)
pulse.scale = (s, s)
# beat ändert sich nur einmal pro Schlag.
if music.beat != last_beat:
last_beat = music.beat
print("beat", last_beat, "bar", music.bar)
Eine Stimme stummschalten
Klicken Sie, um die zweite Stimme des Stücks aus- und wieder einzuschalten.
from pyxen import *
def start():
world.spawn(name="song", music=Music("theme", loop=True))
def update():
music = world.get(name="song").music
if input.mouse.left.pressed:
part = music.channels[1]
if part.volume > 0.0:
part.volume = 0.0
else:
part.volume = 1.0
In einen Takt springen
from pyxen import *
def start():
world.spawn(name="song", music=Music("theme", loop=True))
def update():
music = world.get(name="song").music
if input.mouse.left.pressed:
music.bar = 4 # direkt in Takt 4 springen
Format
Ein Musik-Asset kann OGG Vorbis (.ogg), MIDI (.mid, .midi) oder ein Tracker-Modul sein — ProTracker (.mod), FastTracker II (.xm), Impulse Tracker (.it) oder Scream Tracker 3 (.s3m).
Das Asset wird über den Basisnamen gefunden: Music("theme") findet theme.ogg, theme.mod oder welche dieser Endungen im Projekt vorhanden ist. Die Endung mitzugeben ist der häufigste Fehler — Music("theme.ogg") sucht nach theme.ogg.ogg und löst eine Ausnahme aus.
Ein Tracker-Modul ist oft nur wenige Kilobyte groß, wo dasselbe Stück als OGG mehrere Megabyte belegt, und es loopt bauartbedingt nahtlos — ideal für ein kleines Spiel.
Es ist auch das Format, das hier am meisten bietet: Kanäle, Schläge, Takte und Tempo funktionieren alle bei einem Modul. MIDI hat Kanäle und Tempo; OGG hat keins von beidem (siehe die Abschnitte Kanäle und Musikalische Zeit).
Module berücksichtigen das Panning: ProTrackers Befehl 8xx verschiebt einen Kanal im Stereobild, während das Stück spielt.
Im Gegensatz zu Sound (WAV, vollständig in den Speicher geladen) wird Music gestreamt und bei Bedarf dekodiert — besser geeignet für längere Audiodateien.
Häufige Fehler
Fehlender Dateiname
Music()
ValueError: Music must have a file
Ungültiges Asset
Music("nonexistent")
Music("theme.ogg") # die Endung gehört an die Datei, nicht in den Namen
ValueError: Music 'nonexistent' not found. Use the asset name without its extension (.ogg, .mid, .mod, .xm, .it, .s3m).