Abfrage der Laufzeit entfernen und weiteres

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das muss schon mit der bestehenden Logik verbaut werden. Und es kann sein, dass du den Pygame Event Loop treiben musst. Also gelegentlich mal aufrufen.
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Hallo,

bin jetzt etwas weiter mit dem Sound, allerdings funktioniert es noch nicht so richtig. Momentan sieht der Code folgendermaßen aus:

Code: Alles auswählen

import gpiozero
import time
import random
import pygame
from itertools import repeat, chain
pygame.init()
sound = pygame.mixer.Sound('/home/pi/Documents/test.wav')

def play_leds(leds):
    on_time = 0.1  # Leuchtdauer der LED
    delays = chain(repeat(on_time, 20 * len(leds) - 1), [5])
    for delay in delays:
        led = random.choice(leds)
        led.on()
        sound.play()
        time.sleep(delay)
        led.off()

def main():
    leds = gpiozero.LEDBoard(17, 18, 27, 22, 23, 24, 25, 4, 12)
    button = gpiozero.Button(16)
    while True:
        button.wait_for_press()
        play_leds(leds)

if __name__ == '__main__':
    main()
Ich vermute mal, dass das nicht optimal ist :?: Ich habe das Problem, dass der Sound, wenn er nicht genau so lang bzw. kurz ist wie das Aufblinken, völlig falsch abgespielt wird. d.h. wenn ich den Sound nicht auf 0.1 Sekunden kürze, funktioniert die Wiedergabe nicht richtig. Und am Ende soll ein ANDERER Sound entsprechend des delays, also hier 5 Sekunden lang abgespielt werden.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Du in 18 sekunde 180 mal einen Sound abspielen willst, dann rate mal, wie lange ein Ton sein kann ...
Was soll man sonst noch sagen? Du mußt das was Du willst programmieren. Das passiert nicht von alleine.
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Natürlich nur 0.1 Sekunden, wie oben geschrieben. Mich interessiert eher, ob denn der Code an sich so ok ist, oder ob der Einbau des sound.play() anders gelöst werden sollte.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Man könnte natürlich auch parallel dazu einen Sound abspielen, der halt so lang ist, wie die ganze Würfelei.
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Ja, die Idee hatte ich auch. Da könnte ich wohl was basteln. Ich muss den Code sowieso noch anpassen, die Würfelei sollte höchstens 10 bis 15 Sekunden dauern. Also entsprechend weniger Durchläufe.
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Moin Forum, kleines Update hierzu. Ich habe den Code erweitert, um insgesamt 5 verschiedene Sounds abzuspielen. Jetzt gibt es nur ein Problem. Obwohl die Sounds vorhanden sind, können sie nicht abgespielt werden. Hier mal der aktuelle Code:

Code: Alles auswählen

import gpiozero
import time
import random
import pygame.mixer
import glob
from itertools import repeat, chain

pygame.mixer.init()
soundfiles = glob.glob("/home/pi/Documents/*.mp3")

def play_leds(leds):
    pygame.mixer.Sound(random.choice(soundfiles)).play()
    on_time = 0.1  # Leuchtdauer der LED
    delays = chain(repeat(on_time, 11 * len(leds) - 1), [5])
    for delay in delays:
        led = random.choice(leds)
        led.on()
        time.sleep(delay)
        led.off()

def main():
    leds = gpiozero.LEDBoard(17, 18, 27, 22, 23, 24, 25, 4, 12)
    button = gpiozero.Button(16)
    while True:
        button.wait_for_press()
        play_leds(leds)

if __name__ == '__main__':
    main()

Sobald das Skript gestartet wurde, und ich den Taster drücke, erscheint folgende Fehlermeldung:

pygame.error: Unable to open file '/home/pi/Documents/versuch5.mp3'

Wobei die Nummer hinter "versuch" immer anders ist, also z.B. versuch3, versuch2 usw. Die "glob" scheint also zu funktionieren, aber warum kann die MP3 nicht geöffnet werden?
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Update: Ich habe jetzt probeweise statt MP3 Dateien Wave Dateien verwendet (die selben MP3 in .wav) umgewandelt, und siehe da: Die Sounds werden tatsächlich OHNE Fehlermeldung abgespielt, auch zufällig. Aber warum klappt das mit MP3 nicht?
Benutzeravatar
__blackjack__
User
Beiträge: 13107
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Frank R.: Kann Pygame (unter Linux) überhaupt MP3? Ist ja ein nicht-freier Codec.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Habe eben mal nachgeschaut, pygame kann MP3 abspielen, aber der Code muss dann anders aussehen. Wenn man nur Sounds in Form von .ogg oder .wav Dateien abspielen möchte, sieht der Code so aus wie oben geschrieben. Wenn man aber MP3 Dateien abspielen will, muss es "pygame.mixer.music.play()" heißen, so habe ich das jedenfalls verstanden. Werde ich bei Gelegenheit mal testen.
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Habe noch eine Frage: Ist es möglich, die Zufallswiedergabe der Sounds so zu coden, dass ein und derselbe Sound NICHT zweimal nacheinander abgespielt wird? Also z.B. test1.wav wird abgespielt, dann folgt zufällig test2.wav bis test5.wav, und erst nach diesem Durchlauf darf test1.wav wieder aufgerufen werden.
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Klar ist das moeglich. Du musst eine Kopie der moeglichen Sounds machen, und bei jeder Auswahl (random.choice(copy_of_available_sounds)) den gewaehlten einfach entfernen. Sobald die Liste dann leer ist, erzeugst du wieder eine Kopie aller Moeglichkeiten.
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

So zB

Code: Alles auswählen

import time
import random

SONGS = ["a", "b", "c"]


def random_song_generator(songs):
    while True:
        selection = list(songs)
        random.shuffle(selection)
        yield from selection


def main():
    rsg = random_song_generator(SONGS)
    while True:
        for _ in SONGS:
            print(next(rsg))
        print("-" * 20)
        time.sleep(.2)


if __name__ == '__main__':
    main()
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Ok, aber wie binde ich diese Funktion in meinen Code ein? So wie ich das verstanden habe muss die Zeile "rsg = random_song_generator(SONGS)" , zusätzlich in "def main():" rein. Nur wie binde ich den Abschnitt "def random_song_generator(songs):" ein? Und was ist mit den pygame.mixer Angaben? Was kann/muss davon weg bzw. geändert werden?
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

`play_leds` braucht als zusätzliches Argument den Sound, der gespielt werden soll.
Und aus der while-True-Schleife machst Du einfach eine for-Schleife über die Sounds, die rsg liefert.
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Jetzt bin ich doch etwas verwirrt. play_leds hat doch bereits eine Zeile zur Soundwiedergabe, "pygame.mixer.Sound(random.choice(soundfiles)).play()", die aber so nicht mehr stimmen kann. Wenn ich das richtig verstanden habe, muss ja zwangsläufig "shuffle" verwendet werden, da nur so eine Wiederholung der selben Datei direkt nacheinander vermieden wird. Und welche while-true Schleife ist gemeint?
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Du willst, dass ein Song nicht mehrfach gespielt wird, also muß die Funktion `play_leds` Wissen über ihren vorherigen Aufruf haben, das geht aber nicht. Daher muß dieses Wissen von außen kommen, über ein Argument.
Ich sehe in Deinem Code nur eine while-Schleife.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Die Lösung tut übrigens nicht ganz das, was der Threadersteller gewünscht hat. Es kann noch immer dazu kommen, dass der selbe Song 2 Mal hintereinander gespielt wird. Nämlich dann, wenn er einmal einmal ans Ende der Liste gemischt wurde und bei dem nächsten Durchlauf an den Anfang. Bei dem Beispielcode mit 3 Elementen in der Liste kann man das sehr gut erkennen, weil es da relativ häufig auftritt. Natürlich wird die Wahrscheinlichkeit kleiner, je mehr Songs enthalten sind, trotzdem kann es vorkommen.

Bei Zufallswiedergaben von Liedern - oder zum Beispiel bei Fragen in einem Ratespiel - würde ich immer so vorgehen, dass ich mir die letzten x Songs / Fragen merke und eine neue ziehe, sollte die sich bereits in den letzten gespielten Elementen befinden.

Code: Alles auswählen

import collections
import random
import time


SONGS = ["a", "b", "c", "d", "e", "f"]


class RandomSongGenerator():

    def __init__(self, songs, no_repetition_before=None):
        if no_repetition_before is None:
            no_repetition_before = len(songs) // 2
        if no_repetition_before >= len(songs):
            raise Exception("Min. songs without repetition must be "
                            "less than all songs in list")
        self.songs = songs
        self.last_songs = collections.deque(maxlen=no_repetition_before)

    def get_next_song(self):
        while True:
            current_songs = list(self.songs)
            random.shuffle(current_songs)
            while current_songs:
                next_song = current_songs.pop(0)
                if next_song not in self.last_songs:
                    self.last_songs.append(next_song)
                    yield next_song
                else:
                    current_songs.append(next_song)


def main():
    rsg = RandomSongGenerator(SONGS)
    while True:
        for _ in SONGS:
            print(next(rsg.get_next_song()))
        print("-" * 20)
        time.sleep(.2)


if __name__ == '__main__':
    main()
Frank R.
User
Beiträge: 38
Registriert: Montag 23. September 2019, 10:10

Also den Code von sparrow habe ich soweit verstanden, zumindest gehe ich davon aus. Aber ich weiß nicht wie ich den integrieren soll. Das ist mir dann doch zu hoch :-(
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Ich habe den Generator mal entfernt, weil man sich den Zustand in der Instanz merken kann, und für dich der Aufruf mit next vielleicht etwas verwirrend ist.

Code: Alles auswählen

import collections
import random
import time


SONGS = ["a", "b", "c", "d", "e", "f"]


class RandomSongGenerator():

    def __init__(self, songs, no_repetition_before=None):
        if no_repetition_before is None:
            no_repetition_before = len(songs) // 2
        if no_repetition_before >= len(songs):
            raise Exception("Min. songs without repetition must be "
                            "less than all songs in list")
        self.songs = songs
        self.last_songs = collections.deque(maxlen=no_repetition_before)
        self.next_songs = []

    def get_next_song(self):
        if not self.next_songs:
            self.next_songs = list(self.songs)
            random.shuffle(self.next_songs)
        while True:
            next_song = self.next_songs.pop(0)
            if next_song not in self.last_songs:
                self.last_songs.append(next_song)
                return next_song
            else:
                self.next_songs.append(next_song)


def main():
    rsg = RandomSongGenerator(SONGS)
    while True:
        for _ in SONGS:
            print(rsg.get_next_song())
        print("-" * 20)
        time.sleep(.2)


if __name__ == '__main__':
    main()
Was genau ist denn jetzt noch unverständlich? Das Instanzieren einer Klasse mit Werten hast du doch bereits in deinem Code und das Aufrufen einer Funktion aus der Instanz doch auch.
Antworten