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

ParameterTypBeschreibung
filenamestrName des Musik-Assets, ohne Endung
volumefloatStart-Lautstärke. Standard 1.0
panfloatStart-Stereoposition, -1.0 bis 1.0. Standard 0.0
loopboolAm 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:

EigenschaftTypL/SBeschreibung
loopboolL/SSchleife aktivieren
volumefloatL/SLautstärke (begrenzt >= 0.0)
panfloatL/SStereo-Panning (-1.0 bis 1.0)
timefloatLAktuelle Wiedergabeposition (Sekunden)
durationfloatLGesamtlänge (Sekunden)
finishedboolLTrue wenn Wiedergabe abgeschlossen
channelslistenartigLDie Stimmen des Stücks, jede mit eigenem volume — siehe Abschnitt Kanäle
beatintL/SSchläge seit Wiedergabebeginn. Zuweisen springt an eine Stelle — siehe Musikalische Zeit
barintL/STakte seit Wiedergabebeginn. Zuweisen springt an eine Stelle
phasefloatLWie weit der aktuelle Schlag fortgeschritten ist, 0.0 bis 1.0
beats_per_barintLSchläge pro Takt
frames_to_beatintLFrames bis zum nächsten Schlag
tempofloatL/SMultiplikator der Wiedergabegeschwindigkeit — siehe Tempo
bpmintLSchlä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:

EigenschaftTypL/SBeschreibung
indexintLDie Nummer des Kanals
volumefloatL/SLautstä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:

FormatKanäle
Tracker-ModulEiner pro Kanal des Moduls (ein typisches .mod hat 4)
MIDIImmer 16, einer pro MIDI-Kanal. Ein Kanal, den die Datei nicht nutzt, ist einfach still
OGGKeine: 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()
EigenschaftBedeutung
beatGanze 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
barGanze Takte seit Wiedergabebeginn, ebenfalls monoton
phasePosition 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_barSchläge pro Takt
frames_to_beatFrames, 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:

Formatbeat, bar, phase, frames_to_beat, beats_per_bar, bpm
Tracker-ModulVerfügbar. Siehe unten
MIDIVerfügbar, aus der Datei gelesen. Siehe unten
OGGNicht 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).