<commit_before>/**
 * Copyright (C) 2009 Michael 'Mickey' Lauer <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 */

using GLib;

namespace Alsa
{

/**
 * Helper class, encapsulating a sound that's currently playing
 **/
[Compact]
public class PlayingSound
{
    public PlayingSound( string name, int loop, int length, int cid )
    {
        this.name = name;
        this.loop = loop;
        this.length = length;
        this.cid = cid;
        message( "%s %d create", name, cid );
    }
    ~PlayingSound()
    {
        message( "%s %d destroy", name, cid );
    }
    public string name;
    public int loop;
    public int length;
    public int cid;

}

/**
 * Alsa Audio Player
 **/
class AudioPlayer : FreeSmartphone.Device.Audio, FsoFramework.AbstractObject
{
    private FsoFramework.Subsystem subsystem;
    private Sound.Scenario scenario;
    private Canberra.Context context;
    private HashTable<string,PlayingSound> sounds;
    private Queue<string> scenarios;

    public AudioPlayer( FsoFramework.Subsystem subsystem )
    {
        this.subsystem = subsystem;

        subsystem.registerServiceName( FsoFramework.Device.ServiceDBusName );
        subsystem.registerServiceObject( FsoFramework.Device.ServiceDBusName,
                                         FsoFramework.Device.AudioServicePath,
                                         this );

        sounds = new HashTable<string,PlayingSound>( str_hash, str_equal );

        Canberra.Context.create( &context );
        scenario = new Sound.Scenario();

        logger.info( "created." );
    }

    public override string repr()
    {
        return "<ALSA>";
    }

    public void onPlayingSoundFinished( Canberra.Context context, uint32 id, Canberra.Error code )
    {
        logger.debug( "sound finished with name %s, code %s".printf( (string)id, Canberra.strerror( code ) ) );
        weak PlayingSound sound = sounds.lookup( (string)id );
        assert ( sound != null );

        if ( sound.loop-- > 0 )
        {
            Canberra.Proplist p = null;
            Canberra.Proplist.create( &p );
            p.sets( Canberra.PROP_MEDIA_FILENAME, sound.name );

            Canberra.Error res = context.play_full( (uint32)id, p, onPlayingSoundFinished );
        }
        else
        {
            sounds.remove( (string)id );
            //FIXME send stopped signal
        }
    }

    //
    // DBUS API
    //
    public string[] get_available_scenarios() throws DBus.Error
    {
        string[] list;
        scenario.list( out list );
        return list;
    }
<commit_msg>fsodevice: use eventfd mechanism for signalizing sound repetition. As per libcanberra docs, the finishing callback can be called from whatever stack frame or thread, so we have to use eventfd in order to prevent deadlocks.<commit_after>/**
 * Copyright (C) 2009 Michael 'Mickey' Lauer <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 */

using GLib;

namespace Alsa
{

/**
 * Helper class, encapsulating a sound that's currently playing
 **/
[Compact]
public class PlayingSound
{
    public PlayingSound( string name, int loop, int length, int cid )
    {
        this.name = name;
        this.loop = loop;
        this.length = length;
        this.cid = cid;
        //message( "%s %d create", name, cid );
    }
    ~PlayingSound()
    {
        //message( "%s %d destroy", name, cid );
    }
    public string name;
    public int loop;
    public int length;
    public int cid;
    public bool finished;
}

/**
 * Alsa Audio Player
 **/
class AudioPlayer : FreeSmartphone.Device.Audio, FsoFramework.AbstractObject
{
    private FsoFramework.Subsystem subsystem;
    private Sound.Scenario scenario;
    private Canberra.Context context;
    private HashTable<string,PlayingSound> sounds;
    private Queue<string> scenarios;

    private FsoFramework.Async.EventFd eventfd;

    public AudioPlayer( FsoFramework.Subsystem subsystem )
    {
        this.subsystem = subsystem;

        subsystem.registerServiceName( FsoFramework.Device.ServiceDBusName );
        subsystem.registerServiceObject( FsoFramework.Device.ServiceDBusName,
                                         FsoFramework.Device.AudioServicePath,
                                         this );

        sounds = new HashTable<string,PlayingSound>( str_hash, str_equal );

        Canberra.Context.create( &context );
        scenario = new Sound.Scenario();

        eventfd = new FsoFramework.Async.EventFd( 0, onAsyncEvent );

        logger.info( "created." );
    }

    public override string repr()
    {
        return "<ALSA>";
    }

    /* CAUTION: Note the following quote from the libcanberra API documentation:
     * "The context this callback is called in is undefined, it might or might not be
     * called from a background thread, and from any stack frame. The code implementing
     * this function may not call any libcanberra API call from this callback -- this
     * might result in a deadlock. Instead it may only be used to asynchronously signal
     * some kind of notification object (semaphore, message queue, ...).
     */
    public void onPlayingSoundFinished( Canberra.Context context, uint32 id, Canberra.Error code )
    {
        logger.debug( "sound finished with name %s, code %s".printf( (string)id, Canberra.strerror( code ) ) );
        weak PlayingSound sound = sounds.lookup( (string)id );
        assert ( sound != null );
        sound.finished = true;

        if ( code == Canberra.Error.CANCELED || sound.loop == 0 )
        {
            sounds.remove( (string)id );
            //FIXME send signal
        }
        else
        {
            // wake up mainloop to repeat
            eventfd.write( (int)id );
        }
    }

    public bool onAsyncEvent( IOChannel channel, IOCondition condition )
    {
        //message( "async trigger" );
        uint value = eventfd.read();
        //message( "on async event: %u", value );
        foreach ( var name in sounds.get_keys() )
        {
            weak PlayingSound sound = sounds.lookup( name );
            if ( sound.finished && sound.loop-- > 0 )
            {
                sound.finished = false;

                Canberra.Proplist p = null;
                Canberra.Proplist.create( &p );
                p.sets( Canberra.PROP_MEDIA_FILENAME, sound.name );

                Canberra.Error res = context.play_full( sound.cid, p, onPlayingSoundFinished );
            }
            else
            {
                sounds.remove( name );
            //FIXME send stopped signal
            }
        }
        return true; // MainLoop: call me again
    }

    //
    // DBUS API
    //
    public string[] get_available_scenarios() throws DBus.Error
    {
        string[] list;
        scenario.list( out list );
        return list;
    }

{'fraction_non_alphanumeric': 0.08838568298027757, 'fraction_numerical': 0.005356708059410762, 'mean_word_length': 2.3005222981116913, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6531503', 'n_tokens_mistral': 2288, 'n_tokens_neox': 2191, 'n_words': 778}