Obligement - L'Amiga au maximum

Mercredi 18 décembre 2024 - 03:17  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in English


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


En pratique : Présentation et utilisation de Reggae
(Article écrit par Grzegorz Kraszewski et extrait de MorphOS Library - octobre 2010)


1. Introduction

Reggae est le nom du sous-système modulaire de MorphOS pour la gestion des fichiers multimédias (actuellement des images et des sons, la vidéo et d'autres contenus viendront dans le futur). Reggae est implémenté en tant qu'un large ensemble de bibliothèques partagées stockées dans le répertoire MOSSYS:Classes/Multimedia/. Les classes Reggae tierces peuvent être copiées dans SYS:Classes/Multimedia/. En utilisant Reggae, un développeur d'applications peut facilement réaliser les tâches suivantes liées au traitement des médias :
  • Reconnaissance du type et du format du média.
  • Diffusion de médias via différents supports.
  • Démultiplexage des flux de médias composés.
  • Décodage des médias au format simple et non compressé.
  • Traitement avec application de filtres.
  • Présentation des médias à l'utilisateur.
  • Encodage et multiplexage.
Reggae est une structure orientée objet. Chaque tâche de traitement des médias crée un tuyau ("pipe") ou arbre d'objets Reggae connectés les uns aux autres. Les données médias transitent le long de cette structure en blocs relativement petits. Ce traitement en pipeline permet de traiter de très grosses données, bien plus grandes que la mémoire système disponible.

2. Vue d'ensemble

Cette section contient des informations générales sur Reggae, ses principes de conception, ses modèles d'utilisation et ses règles.

2.1 Types de classes de Reggae

Multimedia.class

C'est la classe principale de Reggae. Elle établit les méthodes et les attributs de base. Elle effectue également l'initialisation de Reggae lorsqu'elle est ouverte pour la première fois après le démarrage. Toutes les autres classes sont des sous-classes de multimedia.class. Cette classe est également responsable de la détection des formats de données et de la construction automatique de l'arbre de décodage. Elle fournit également des fonctionnalités secondaires comme la journalisation des événements, la gestion des métadonnées, les allocations de mémoire adaptées à AltiVec, etc. Grâce à toutes ces fonctionnalités, multimedia.class est la seule et unique classe Reggae possédant une API de bibliothèque partagée, à l'exception de celle de BOOPSI (objet).

Flux

Les flux ("streams") constituent la couche d'abstraction des données d'entrée de Reggae. Un flux est toujours le premier objet dans toute structure de traitement Reggae. Il possède un port de sortie. Tous les flux ont un ensemble commun d'attributs et de méthodes pour récupérer et contrôler les données. Les flux actuellement disponibles sont :
  • memory.stream pour accéder aux données en mémoire : avec tampon, générées ou intégrées dans le code de l'application.
  • file.stream pour la lecture de fichiers via la bibliothèque DOS.
  • http.stream est une implémentation facile à utiliser mais puissante du client du protocole HTTP/1.1. Il peut récupérer toute ressource réseau disponible via une requête HTTP GET.
Alors que memory.stream et file.stream sont des adaptateurs ("wrappers") relativement simples, http.stream est un composant autonome utile ainsi qu'une source de données Reggae. N'importe quelle application peut l'utiliser pour télécharger facilement des données via HTTP, sans même toucher à l'API des interfaces de communication ("sockets") et sans avoir à gérer les éléments internes de HTTP.

Démultiplexeurs

Chaque format de média reconnu par Reggae possède son propre démultiplexeur. La classe Demuxer est responsable de la reconnaissance du format, du décodage des en-têtes, de l'extraction des métadonnées et du démultiplexage (d'où le nom) des flux de médias vers des ports de sortie séparés. Bien qu'il n'y ait pas de véritable démultiplexage dans les formats simples d'audio ou d'image, la division de la fonctionnalité entre le démultiplexeur et le décodeur permet de réutiliser le code, car plusieurs démultiplexeurs peuvent utiliser la même classe de décodeur (par exemple, tous les démultiplexeur pour les formats audio utilisant le format PCM non compressé, utilisent audiopcm.decoder).

Il existe également quelques démultiplexeurs "généraux" qui ne sont pas associés à un format de données particulier. Ils gèrent soit des métadonnées communes (comme id3tag.demuxer), soit des schémas de compression communs au niveau du flux de données (xpk.demuxer). Ces démultiplexeurs sont généralement la première étape de la cascade de démultiplexeurs.

Décodeurs

Un décodeur prend un flux multimédia unique et démultiplexé et le convertit dans l'un des formats communs de Reggae. Cette conversion implique généralement la décompression et le décodage du flux. Certains décodeurs sont dédiés à un format média particulier, d'autres sont plus généraux et utilisés avec de nombreux démultiplexeurs.

Filtres

Un filtre Reggae accepte des données dans l'un des formats communs de Reggae, effectue une transformation sur les données et les restitue dans le même ou dans un autre format commun à Reggae. La plupart des filtres ont le même format en entrée et en sortie, mais ce n'est pas la règle générale. Imaginez un filtre générant des visualisations pour un lecteur audio, il acceptera de l'audio et générera de la vidéo.

Encodeurs

(section vide)

Multiplexeurs

(section vide)

Sorties

Les sorties constituent la couche d'abstraction des sorties de Reggae. Elles ne sont cependant pas symétriques aux flux. Les sorties peuvent être divisées en deux groupes :
  • Les sorties de présentation utilisateur, qui dirigent le flux de données vers un périphérique de sortie d'un ordinateur (audio.output, picture.output).
  • Les sorties de stockage, qui stockent le flux de données quelque part (file.output).
La construction d'une chaîne d'objets Reggae connectés ne lance pas automatiquement le traitement des données. Reggae est un système de type "pull-driven", donc quelque chose doit extraire les données à la fin de la chaîne. Cela peut être l'application, qui appelle activement la méthode MMM_Pull() sur le(s) port(s) de sortie à la fin de la chaîne. Ensuite, elle reçoit simplement les données dans un tampon mémoire et peut en faire ce qu'elle veut. Alternativement, le dernier objet de la chaîne peut être une instance d'une classe de sortie Reggae. Dans ce cas, l'ensemble du traitement est effectué par Reggae.

Chaque objet de sortie Reggae crée un sous-processus qui extrait les données de la chaîne d'objets et les stocke ou les présente à l'utilisateur. Cela signifie que tout le traitement des données Reggae est automatiquement déchargé de l'application vers le sous-processus. Le processus principal de l'application est libre de gérer l'interface graphique, d'afficher la progression du traitement et de contrôler le traitement en exécutant des méthodes sur l'objet de sortie. Le sous-traitement de la chaîne Reggae facilite la création d'applications à base d'interface graphique et permet, par exemple, le traitement en arrière-plan, simplement en fixant la priorité du sous-processus en dessous de 0. Cela peut être utile pour les sorties de stockage. Les sorties de présentation utilisateur doivent travailler en temps réel, leurs priorités par défaut sont donc supérieures à 0.

Classes internes

Il existe quelques classes d'aide, qui sont utilisées par Reggae en interne, mais qui peuvent être intéressantes pour les développeurs avancés de Reggae. Voici une brève description de ces classes :
  • processblock.class est utilisé pour regrouper des ensembles d'objets connectés en groupes vus comme un seul objet de l'extérieur. Par exemple, l'appel MediaNewObject() renvoie un objet "unique", qui est en fait un arbre complet composé d'au moins quatre objets (un flux, un tampon multilecture, un démultiplexeur et un décodeur).
  • multiread.buffer implémente un tampon FIFO avec une fonctionnalité de repérage ("peeking") des données. Il est utilisé dans le processus de reconnaissance de format pour présenter le même en-tête de flux à plusieurs routines de reconnaissance, même si le flux source n'est pas consultable.
Voir les autodocs de ces classes pour plus de détails.

2.2 Formats communs de Reggae

Le format commun Reggae ou le format média de base est le résultat final du décodage des médias, ainsi que le format intermédiaire pour le traitement (avec des filtres) et le format d'entrée pour l'encodage et le multiplexage. Ces formats ne sont que des échantillons PCM bruts pour l'audio et des pixels ARGB bruts pour la vidéo. La description du flux, comme le nombre de canaux audio ou les dimensions de la vidéo, est fournie par des attributs.

Formats audio communs

Tous les formats audio communs sont des flux d'échantillons PCM linéaires. Les flux multicanaux sont entrelacés. Les flux stéréo sont entrelacés dans l'ordre [gauche, droite]. L'entrelacement pour plus de canaux n'est pas encore défini. Trois formats d'échantillons sont définis :
  • MMFC_AUDIO_INT16 - le format le plus commun, les échantillons sont des entiers signés de 16 bits. Le meilleur pour le traitement en temps réel ciblé sur audio.output.
  • MMFC_AUDIO_INT32 - très haute qualité, mais traitement lent. Entiers signés 32 bits. Peut être utile pour le traitement non temps réel. Notez que certains filtres peuvent ne pas le gérer.
  • MMFC_AUDIO_FLOAT32 - un compromis entre les deux ci-dessus. Les échantillons sont des flottants IEEE 754 de simple précision avec une plage normalisée à <-1.0, +1.0>. L'utilisation de flottants permet d'éviter plus facilement le débordement interne, mais les erreurs d'arrondi peuvent être dangereuses.
Formats vidéo communs

Le format vidéo commun est simplement un tableau rectangulaire de pixels. L'ordre de balayage des rangées est de haut en bas, celui des lignes de gauche à droite. Les lignes ne sont en aucun cas remplies. Il n'y a qu'un seul format de pixel défini actuellement :
  • MMFC_VIDEO_ARGB32 - chaque pixel prend 4 octets, 8 bits par composant, 8 bits pour le canal alpha non prémultiplié. L'ordre des composants du pixel est [A, R, G, B].
3.1 Tutoriel 1 : accéder à Reggae dans les applications

Reggae est piloté à partir d'une application avec deux API. L'une d'elles est l'API générique orientée objet de BOOPSI avec ses DoMethod(), GetAttr(), SetAttrs(), etc. La seconde API est juste une bibliothèque MorphOS partagée, fournie par multimedia.class et est, bien sûr, spécifique à Reggae. Pour utiliser Reggae, il faut ouvrir multimedia.class comme toute bibliothèque partagée. Pour l'API BOOPSI, intuition.library doit être ouverte, ce que presque tous les programmes font de toute façon.

Ouverture et fermeture de multimedia.class

La première étape consiste à ajouter les fichiers d'inclusion nécessaires :

#include <proto/multimedia.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <clib/alib_protos.h>

Le premier fichier contient la définition de l'API de la bibliothèque multimedia.class. Il inclut également <classes/multimedia/multimedia.h>, contenant les structures, les constantes, les balises et les macros Reggae. Le reste des fichiers d'inclusion n'est pas spécifique à Reggae, en fait la plupart des projets les incluent de toute façon, car ils définissent les services de base du système. Il existe également des en-têtes Reggae supplémentaires, leur inclusion dépend de l'application et sera couverte dans d'autres tutoriels.

Nous sommes maintenant prêts pour l'initialisation de Reggae. Tout ce qui doit être fait est d'ouvrir multimedia.class comme une bibliothèque partagée ordinaire de MorphOS. La seule différence notable est que le nom de la bibliothèque contient une partie chemin, car les classes Reggae ne sont pas directement sur le chemin de recherche de la bibliothèque :

struct Library* MultimediaBase;

if (MultimediaBase = OpenLibrary("multimedia/multimedia.class", 52))
{
/* Maintenant Reggae est prêt à être utilisé jusqu'à ce que la bibliothèque soit fermée. */
  CloseLibrary(MultimediaBase);
}

Comme pour toute bibliothèque, le nom de la base est important, car il est implicitement utilisé dans tous les appels à l'API de la bibliothèque en tant que paramètre caché. Notez également la gestion habituelle des erreurs, multimedia.class est basé sur le disque et effectue également une certaine activité disque au démarrage, alors il est recommandé de vérifier la base de la bibliothèque par rapport à NULL. "52" est la version actuelle du multimedia.class. Les applications devraient demander cette version, car les précédentes manquent de certaines fonctionnalités importantes. Le nettoyage de la bibliothèque est fait avec l'appel classique CloseLibrary().

Note : certains programmeurs utilisent l'ouverture et la fermeture automatiques des bibliothèques en les liant avec libauto. multimedia.class peut être ajouté à la liste des bibliothèques ouvertes automatiquement, mais il ne l'est pas par défaut.

Un exemple complet de code (NDLR : fichier à présent indisponible) montre l'initialisation de Reggae. L'exemple ouvre multimedia.class et, s'il a réussi, affiche la version et la révision de multimedia.class sur la console.

Ouverture et fermeture de classes individuelles

L'utilisation typique de Reggae consiste à appeler MediaNewObject() pour qu'un flux de données soit reconnu, démultiplexé et décodé automatiquement dans les formats communs. Dans ce cas, Reggae ouvre (et plus tard ferme) toutes les classes nécessaires automatiquement. Les choses changent lorsque l'application construit la chaîne de traitement à la main, ou utilise simplement des composants uniques. Alors chaque classe utilisée doit être ouverte et fermée explicitement. Cela se fait de la même manière qu'avec multimedia.class. Supposons que notre application n'utilise que l'objet http.stream pour télécharger des données depuis Internet. Avant la création de l'objet, la classe doit être ouverte. Cela est généralement fait dans le code de configuration de l'application :

struct Library *HttpStreamBase;

HttpStreamBase = OpenLibrary("multimedia/http.stream", 51);

Comme pour toute bibliothèque sur disque, il est fortement recommandé de vérifier la base par rapport à NULL. La classe doit être déchargée après utilisation, par exemple lors du nettoyage de l'application :

CloseLibrary(HttpStreamBase);

Les classes Reggae sont gérées comme des bibliothèques partagées ordinaires. Notez que le chemin "multimedia/" est ajouté au nom de la classe, la raison de ceci a été expliquée ci-dessus. Lorsqu'une classe n'est pas utilisée en permanence dans une application, il peut être judicieux de l'ouvrir juste avant d'en avoir besoin et de la fermer dès qu'elle n'est plus nécessaire. Par exemple, si http.stream n'est utilisée que pour les mises à jour automatiques de l'application, il n'est pas nécessaire de la garder ouverte après la vérification/le téléchargement de la mise à jour.

La dernière remarque concerne les noms des bases de bibliothèque pour les classes. Aucune classe Reggae, à l'exception de multimedia.class, n'a d'API de type bibliothèque. Pour cette raison, le nom de la base de bibliothèque n'est pas utilisé pour autre chose que l'ouverture et la fermeture de la classe. C'est pourquoi le nom n'est pas important et peut être n'importe quoi. Dans ces tutoriels, les noms de bases dérivés des noms de classes sont utilisés, juste pour améliorer la lisibilité du code.

Je n'aime pas C/C++, que faire ?

Reggae peut être utilisé à partir de n'importe quel langage de programmation. Il doit cependant répondre à un ensemble minimal de caractéristiques. La première est la capacité d'appeler des fonctions des bibliothèques partagées standard de MorphOS, intuition.library et exec.library au moins. Ensuite, la possibilité d'appeler des fonctions de multimedia.class. La plupart des langages de programmation utilisés sur MorphOS ont des outils pour créer des liens ("bindings" ou "stubs", ou quel que soit leur nom) vers des bibliothèques partagées, basées sur des en-têtes C et des fichiers *.fd. Créer des liens pour multimedia.class ne devrait donc pas être difficile.

Ensuite, le langage doit disposer de l'appel DoMethod(). Pour C/C++, cet appel est fourni par libabox, une bibliothèque statique. Une autre caractéristique importante est la capacité de gérer les fonctions basées sur les balises d'une manière sensée. La liste des balises peut toujours être construite à la main, mais il est pratique de passer les balises comme arguments aux fonctions à args variables.

NewObject() et MediaNewObject()

Ces deux fonctions ont des noms similaires, mais elles sont différentes. Les développeurs utilisant MUI peuvent penser que MediaNewObject() est une sorte d'adaptateur/enveloppe autour de NewObject(), tout comme MUI_NewObject() est un tel adaptateur/enveloppe. Cependant, ce n'est pas vrai pour MediaNewObject(). NewObject() est un appel basique de BOOPSI, et crée simplement un objet de la classe spécifiée. Les objets Reggae sont également créés avec cette fonction.

MediaNewObject() crée un arbre complet d'objets nécessaires au décodage d'un flux média vers les formats de base. L'arbre est constitué d'un objet de flux, d'un ou plusieurs objets de démultiplexage, d'un ou plusieurs objets de décodage et de quelques objets auxiliaires comme des tampons de lecture multiple ou des blocs de traitement. L'arbre entier est présenté à l'application comme un objet unique délivrant des flux décodés sur ses ports de sortie.

Une autre différence entre les deux fonctions est le chargement automatique des classes Reggae. L'appel NewObject() ne charge pas automatiquement en mémoire une classe BOOPSI basée sur le disque. Une application doit donc charger manuellement une telle classe avec OpenLibrary() avant la création de l'objet (et la décharger après utilisation avec CloseLibrary()). En revanche, MediaNewObject() ouvre automatiquement toutes les classes nécessaires et se charge de les décharger.

Contrôle des erreurs

De nombreuses opérations sur les objets Reggae peuvent échouer pour différentes raisons. Il peut s'agir d'un manque de mémoire, d'une erreur d'entrées/sorties, de mauvais arguments passés par l'application, etc. Une bonne pratique de programmation consiste à vérifier les résultats des opérations et à prendre les mesures appropriées en cas d'échec. La première étape de la vérification consiste à contrôler la valeur de retour de la méthode. Sa signification exacte dépend de la méthode elle-même, et est décrite dans les autodocs. Les cas les plus courants sont décrits ci-dessous.

La deuxième étape de la vérification des erreurs consiste à obtenir un code d'erreur de l'opération. Ceci est fait en obtenant une valeur d'attribut MMA_ErrorCode d'un objet. Les valeurs des codes d'erreur sont définies dans le fichier d'en-tête <classes/multimedia/multimedia.h>. La méthode habituelle consiste simplement à obtenir l'attribut d'un objet, ce qui est fait avec l'appel BOOPSI GetAttr().

LONG error;

GetAttr(object, MMA_ErrorCode, (ULONG)&error);

On peut aussi obtenir l'attribut par l'un des ports de l'objet (entrée ou sortie, peu importe) :

error = MediaGetPort(object, 0, MMA_ErrorCode);

Erreurs lors de la création d'un objet

La création d'un objet est un cas particulier dans la gestion des erreurs pour deux raisons. Premièrement, c'est l'endroit le plus probable pour les erreurs, car la création d'un nouvel objet Reggae peut être une opération complexe impliquant la création de nombreux objets de base, leur connexion, l'exécution d'opérations d'entrées/sorties sur la source de données, l'analyse des en-têtes de flux, etc. Deuxièmement, si la création d'un objet échoue, il n'y a pas d'objet pour demander l'attribut MMA_ErrorCode. C'est pourquoi MMA_ErrorCode peut être passé à la méthode OM_NEW() (ce qui signifie que NewObject() et MediaNewObject() l'acceptent), et sa valeur de balise contient un pointeur vers une variable, où le code d'erreur sera placé. L'exemple de code ci-dessous montre la gestion des erreurs lors de la création d'un objet :

Object *obj;
LONG error = 0; /* initialisation est recommandée */

if (obj = MediaNewObjectTags( /* n'importe quelle balise ici */ , MMA_ErrorCode, (IPTR)&error, TAG_END))
{
 /* use object */
}
else
{
 /* le code d'erreur se trouve dans la variable 'error',
    il faut prendre des mesures, informer l'utilisateur, etc. */
}

Erreurs pendant l'extraction des données

La première étape de la vérification des erreurs dans ce cas consiste à vérifier la valeur de retour de la méthode MMM_Pull(). S'il n'y a pas d'erreur, elle devrait être égale à la quantité d'octets demandés. Si ce n'est pas le cas, on doit obtenir la valeur MMA_ErrorCode, comme décrit ci-dessus. Il y a cependant un cas où la valeur renvoyée par MMM_Pull() ne sera pas égale à la taille de la demande, et MMA_ErrorCode sera zéro. Cela peut arriver lorsque la taille de la demande n'est pas alignée sur le plus petit morceau autorisé par Reggae. Ce morceau est un pixel unique pour la vidéo, et une trame d'échantillon unique pour l'audio. Une telle requête mal alignée est tronquée silencieusement et seuls les morceaux entiers sont renvoyés. C'est pourquoi l'alignement de la taille de la demande sur celle d'un pixel ou d'une trame d'échantillon devrait être une règle de base lors de la programmation de Reggae. Le fragment de code ci-dessous montre la vérification des erreurs après MMM_Pull() :

if ((pulled = DoMethod(MMM_Pull, object, port, buffer, length)) == length)
{
 /* L'extraction des données a réussi. */
}
else
{
 LONG error = 0;

 GetAttr(objet, MMA_ErrorCode, (IPTR)&error);

 /* L'extraction des données a échoué, mais certaines données peuvent être valides (le nombre d'octets
    de données valides est stocké dans 'pulled'). Le code d'erreur est dans 'error'.*/
}

Affichage des messages d'erreur

Certaines erreurs de Reggae (en particulier celles qui sont attendues, comme les erreurs de fichier ou d'entrées/sorties réseau) doivent être signalées à l'utilisateur. Notez que pour les erreurs moins probables, les utilisateurs avancés peuvent utiliser MediaLogger, qui affiche le journal des événements de Reggae. Les problèmes de base doivent cependant être signalés par l'application. Bien sûr, l'affichage d'un code d'erreur cryptique n'est pas recommandé. Le moyen le plus simple est d'utiliser la fonction MediaFault() pour convertir ces codes en messages texte. L'un des grands avantages de MediaFault() est que ses messages sont localisés et qu'elle renvoie automatiquement des textes dans la langue préférée de l'utilisateur. La fonction peut par exemple être utilisée comme suit :

LONG error;

GetAttr(object, MMA_ErrorCode, (IPTR)&error);

if (error) Printf("Operation failed: %s.\n", MediaFault(error));

Cet exemple simple affiche le message sur une console, les programmes à base d'interface graphique le passeront à MUI_Request() par exemple. Les chaînes de caractères retournées par MediaFault() ne sont pas capitalisées et ne comportent pas de point à la fin.

3.2 Tutoriel 2 : téléchargement de ressources Web avec http.stream - notions de base

La classe http.stream est l'une des classes de flux de Reggae, autrement dit des sources de données. Dans une chaîne d'objets Reggae, une instance http.stream sera toujours le premier objet, avec un seul port de sortie. Un objet http.stream peut également être utilisé de manière autonome, sans être connecté à quoi que ce soit, juste pour récupérer n'importe quelle ressource de données accessible via le protocole HTTP et notamment ses requêtes GET et POST. De ce point de vue, http.stream n'est qu'un client HTTP/1.1 intégrable doté d'une API simple mais puissante. Une brève liste de ses caractéristiques est donnée ci-dessous :
  • Encapsulation de l'API des interfaces de communication. http.stream isole complètement l'application (et son programmeur) de la bibliothèque bsdsocket et de la pile TCP/IP. Seules des connaissances très basiques de TCP/IP sont nécessaires pour utiliser http.stream avec succès.
  • Contrairement aux instances de base de bsdsocket.library, les objets http.stream peuvent être partagés entre les processus (à la seule exception que l'objet doit être éliminé par le processus qui l'a créé).
  • La classe possède un analyseur syntaxique intégré des en-têtes de réponse HTTP.
  • La classe a également un constructeur d'en-tête de requête HTTP facile à utiliser, de sorte que des champs personnalisés peuvent être ajoutés à l'en-tête.
  • Les proxies HTTP sont gérés.
  • La classe gère le transfert par morceaux et le flux multimédia sur HTTP.
  • L'usurpation d'agent utilisateur est possible en option.
  • Lors de la connexion, les redirections HTTP peuvent être suivies automatiquement.
  • La classe est capable de gérer des flux de plus de 4 Go.
  • Débogage facile du protocole via MediaLogger.
Cette classe présente toutefois quelques inconvénients. Certains d'entre eux pourraient être supprimés dans les futures versions :
  • Pas de gestion pour HTTP Secure en dessous de la v52.
  • Pas de gestion pour les connexions persistantes.
  • Pas de gestion pour la compression HTTP.
  • L'établissement de la connexion, l'envoi de la requête et la réception de l'en-tête de réponse sont effectués dans le constructeur, donc ils sont synchrones avec l'application. Tout retard du réseau dans le constructeur bloque l'application jusqu'à ce que le délai d'attente ou une autre erreur soit atteint. Il est possible de contourner ce problème en plaçant toutes les opérations réseau dans un sous-processus.
Exemple minimal

Si nous ne tenons pas compte de la gestion des erreurs, l'ensemble du processus de téléchargement des données via le protocole HTTP se réduit à trois lignes de code :

#define DATA_LENGTH 7465 /* juste une valeur d'exemple */

UBYTE buffer[DATA_LENGTH]; /* place pour les données */
Object *http;

http = NewObject(NULL, "http.stream", MMA_StreamName, "www.morphzone.org", TAG_END);
DoMethod(http, MMM_Pull, 0, buffer, DATA_LENGTH);
DisposeObject(http);

Nous supposons ici que la classe http.stream a été chargée précédemment avec OpenLibrary() (voir paragraphe "Ouverture et fermeture de classes individuelles"). Le code va télécharger les 7465 premiers octets de la page principale de MorphZone (code HTML), en supposant qu'il n'y aura pas d'erreur. Cette supposition est plutôt risquée, car une opération réseau peut échouer pour de nombreuses raisons. Dans ce cas, nous devrons appeler une méthode sur le pointeur NULL et disposer de NULL plus tard, ce qui peut même conduire à un plantage de l'application. C'est pour cette raison que http.stream offre plusieurs façons de gérer les erreurs. Elles seront abordées plus tard, mais pour l'instant, une gestion minimale des erreurs consiste à vérifier le résultat de NewObject() par rapport à NULL. Ceci est utilisé dans un exemple simple qui télécharge les 1000 premiers octets d'une ressource spécifiée dans la ligne de commande et les envoie dans la console. Notez que l'utilisation de ce programme pour des ressources binaires (comme des images) peut donner des résultats plutôt étranges... Je recommande d'exécuter cet exemple avec MediaLogger, pour apprendre les fonctionnalités de débogage du protocole http.stream.

Longueur des données

L'utilité de l'exemple ci-dessus est limitée. Il ne télécharge que la quantité prédéfinie de données (ou moins, si la ressource s'avère être plus courte). En général, nous voulons télécharger toutes les données, ce qui implique d'obtenir leur longueur d'une manière ou d'une autre. Quelques scénarios sont possibles :

1. La longueur des données est connue avant le téléchargement. C'est le cas le plus simple, mais le plus rare. Il peut être traité exactement comme dans l'exemple de la section précédente - un tampon de taille statique et un seul appel MMM_Pull().

2. Le serveur envoie un fichier statique. Il connaît alors la taille et la transmet dans l'en-tête de réponse (champ Content-Length). L'objet http.stream l'extrait automatiquement. Ensuite, la longueur des données peut être obtenue en récupérant l'attribut MMA_StreamLength. Cela signifie que la longueur est connue avant le téléchargement des données, de sorte qu'un tampon peut être alloué dynamiquement. L'attribut est de 64 bits, il doit donc être obtenu comme suit :

QUAD length;

length = MediaGetPort64(http, 0, MMA_StreamLength);

Cet exemple montre l'utilisation de MMA_StreamLength. Il crée l'objet, demande la longueur des données, alloue un tampon, télécharge les données dans le tampon et enfin stocke le tampon dans un fichier.

3. Le serveur envoie des données générées dynamiquement. Les données sont généralement générées par un script côté serveur, écrit en PHP ou dans un autre langage. Dans ce cas, le serveur ne connaît pas la longueur a priori, il passe donc en mode de transfert HTTP chunked. L'objet http.stream s'en charge automatiquement, et rapporte 0 comme MMA_StreamLength, ce qui signifie que la longueur est inconnue. La seule façon de traiter de telles données est de les télécharger par blocs dans une boucle jusqu'à ce que l'objet signale le code d'erreur MMERR_END_OF_DATA. Le code de la boucle peut ressembler à ceci :

LONG chunk, error = 0;

while (!error)
{
  chunk = DoMethod(http, MMM_Pull, 0, buffer, BUFFER_SIZE);

  /* Faire quelque chose avec les octets de données de 'chunk' dans 'buffer'. */

  if (chunk < BUFFER_SIZE)
  {
    if (MediaGetPort(http, 0, MMA_ErrorCode) == MMERR_END_OF_DATA))
    {
      break; /* téléchargement terminé */
    }
    else
    {
      error = 1; /* le téléchargement a échoué */
    }
  }
}

La même boucle, simplement enrichie de rapports de progression et d'erreur, est utilisée dans l'exemple complet, qui n'est qu'une application de téléchargement HTTP universelle, très simple, basée sur Reggae. Ce qui peut être intéressant, c'est qu'il traite correctement les données de plus de 4 Go, en supposant que le système de fichiers du fichier de destination soit en 64 bits.

Il est important de noter que la RFC 2616, la spécification HTTP, ne précise pas que les fichiers statiques doivent être servis sans transfert par morceaux. D'autre part, le serveur n'est pas obligé d'utiliser le transfert par blocs pour les contenus générés dynamiquement. L'hypothèse selon laquelle le serveur n'utilisera pas de blocs pour un fichier simplement parce qu'il s'agit d'un fichier statique peut échouer. Dans ce cas, le moyen le plus sûr est de toujours utiliser la boucle de téléchargement et de traiter MMA_StreamLength comme une indication seulement.

3.3 Tutoriel 3 : écriture de classes Reggae

Les fonctionnalités de Reggae peuvent être étendues en lui ajoutant des classes. Les nouvelles classes peuvent être soit publiques, soit privées.

Classes Reggae privées

Les classes privées sont simples. Elles sont liées statiquement à une application et ne peuvent être utilisées que dans cette seule application. Une classe privée Reggae est simplement une classe BOOPSI dérivée de multimedia.class et implémentant l'ensemble standard des méthodes Reggae. La classe est créée avec MakeClass() et référencée ultérieurement par un pointeur.

Classes Reggae publiques

Une classe Reggae publique est plus compliquée. Il s'agit d'un composant séparé sur disque, qui peut être chargé par n'importe quelle application. Les classes publiques utilisent le cadre des bibliothèques partagées, elles ne sont donc qu'un cas particulier des bibliothèques partagées de MorphOS. Les différences sont les suivantes :
  • La classe Reggae publique n'a pas d'API de bibliothèque partagée, à l'exception des vecteurs standard open, close et expunge.
  • La classe crée une classe BOOPSI nommée à la première ouverture avec MakeClass() et l'ajoute à la liste du système avec AddClass(). Ensuite, la classe peut être utilisée par plusieurs applications (le code est partagé) et est référencée par son nom, de la même manière qu'une bibliothèque partagée ordinaire.
L'exemple suivant montre la différence dans la création d'objets de classe publique et privée :

Object *pub, *priv;

priv = NewObject(PrivClassPointer, NULL, /* tags */, TAG_END);
pub = NewObject(NULL, "public.class", /* tags */, TAG_END);

Bien entendu, une classe publique doit être ouverte avec OpenLibrary() avant d'être utilisée (voir paragraphe "Ouverture et fermeture de classes individuelles") et fermée lorsqu'elle n'est plus utilisée.

3.4 Tutoriel 4 : lecture d'un son à partir d'un fichier

La lecture d'un fichier son à partir d'un disque est l'une des tâches les plus courantes liées aux médias. Reggae peut l'exécuter avec quelques lignes de code. L'utilisation de Reggae pour la lecture audio présente plusieurs avantages :
  • Une large gamme de formats audio gérés. Un codec est sélectionné et chargé automatiquement par Reggae.
  • La lecture est asynchrone. Reggae décharge le décodage et la lecture sur un processus dédié. L'application principale peut effectuer d'autres tâches pendant la lecture. Elle est informée lorsque la lecture se termine.
  • Reggae diffuse l'audio depuis le disque, il ne charge donc pas le fichier entier en mémoire. La mise en mémoire tampon double est entièrement automatique.
  • L'audio est lu par l'unité AHI sélectionnée. Plusieurs sons (jusqu'à 32, selon les paramètres AHI) peuvent être lus simultanément.
La lecture de l'audio directement à partir du disque convient mieux aux sons longs sans exigences de faible latence. Un exemple typique est un lecteur de musique ou la lecture de musique de fond dans un jeu.

Du point de vue de Reggae, la tâche de lecture audio à partir d'un disque peut être divisée en deux parties principales. La première consiste à extraire les échantillons audio bruts du fichier codé. La deuxième tâche consiste à envoyer les données audio vers la sortie.

Ouverture d'un fichier son

Cette partie du travail est hautement automatisée. Reggae reconnaît le format du fichier et construit un pipeline de décodage complet pour le fichier avec un seul appel de fonction. Le résultat est renvoyé à l'application sous la forme d'un objet opaque (il peut contenir de nombreux objets, mais cela n'a aucune importance pour le développeur de l'application).

Object* media;

media = MediaNewObject(
  MMA_StreamType, (ULONG)"file.stream",
  MMA_StreamName, (ULONG)"RAM:sound",
  MMA_MediaType, MMT_SOUND,
TAG_END);

Cet appel unique crée une infrastructure de décodage complète pour un fichier spécifié. La source de données est spécifiée par deux balises, MMA_StreamName et MMA_StreamType. La première est le nom de la source. Dans le cas des fichiers, il s'agit simplement du chemin d'accès au fichier, qui peut être absolu (comme dans l'exemple), relatif au répertoire actuel, ou relatif à l'emplacement de l'exécutable du programme (en utilisant l'assignation PROGDIR:). MMA_StreamType est utilisé pour spécifier quelle classe de flux Reggae (ou "transport") doit être utilisée. Bien sûr, file.stream est pour les fichiers sur disque (et d'autres choses reconnues par DOS comme des systèmes de fichiers).

La dernière balise est une sorte de filtre. Si Reggae reconnaît le fichier, mais que ce n'est pas du son, le fichier sera rejeté, et la fonction retournera NULL. Bien sûr, si le fichier n'est pas reconnu du tout, NULL sera également retourné. Vérifier le résultat de MediaNewObject() contre NULL est une très bonne idée. En cas de succès, le média contient un pointeur vers un objet Reggae, ayant au moins un port de données de sortie, le port 0.

Création de la sortie

La deuxième étape consiste à ajouter un objet de sortie audio au pipeline de traitement Reggae. On peut alors "exécuter" le pipeline, ce qui a pour effet de lire le fichier. L'objet de sortie appartient à la classe audio.output. Avant de pouvoir créer un objet, la classe doit être chargée depuis le disque. Cela se fait en ouvrant la classe avec OpenLibrary().

struct Library* AudioOutputBase;

AudioOutputBase = OpenLibrary("multimedia/audio.output", 51);

Il est intéressant de noter qu'audio.output n'a pas de fonctions spécifiques dans son API de bibliothèque partagée (c'est vrai pour toutes les classes Reggae sauf la classe principale multimedia.class). Ensuite, le nom de la variable contenant la base de la bibliothèque n'est absolument pas pertinent (car le nom n'est jamais utilisé implicitement), et peut être n'importe quoi, "hja76_d62eg" par exemple. Le nom utilisé dans l'exemple est cependant un peu plus lisible.

Après l'ouverture de la classe, une instance de la classe peut être créée :

Objet* play;

play = NewObject(NULL, "audio.output", TAG_END);

L'instance est créée avec l'appel générique NewObject(). Il n'y a pas de balises pour les attributs. L'objet de sortie lira toutes les propriétés sonores de l'objet média lorsqu'ils seront connectés ensemble. Je rappelle encore une fois que vérifier la valeur de retour ici peut être une bonne idée. Si les objets sont prêts, connectons-les :

MediaConnectTagList(media, 0, play, 0, NULL);

Le port de sortie 0 de l'objet média est connecté au port d'entrée 0 de l'objet "play". Les deux objets forment un pipeline de traitement Reggae complet. Maintenant nous sommes prêts à jouer le son. Le contrôle complet de la lecture se fait en parlant à l'objet "output".

Émettre du son

La lecture est contrôlée par trois méthodes : MMM_Play(), MMM_Stop() et MMM_Pause() exécutées sur l'instance audio.output.
  • MMM_Play() lance la lecture si l'objet est arrêté, elle est ignorée lorsque l'objet est en cours de lecture.
  • MMM_Stop() arrête la lecture et rembobine le flux audio jusqu'au début (si possible).
  • MMM_Pause() (disponible depuis la version 51.14 d'audio.output) arrête la lecture, mais ne rembobine pas le flux audio. La méthode MMM_Play() qui suivra reprendra la lecture à partir de la position de pause.
Toutes les méthodes sont exécutées immédiatement, donc taper simplement :

DoMethod(play, MMM_Play);

...pour lancer la lecture et...

DoMethod(play, MMM_Stop);

...pour l'arrêter à tout moment. Toutes les méthodes sont asynchrones par rapport à l'appelant et redonne la main immédiatement. Même si le temps d'installation de MMM_Play() est long (à cause du pré-tamponnage par exemple), le processus appelant n'est pas arrêté car l'installation est faite par le processus audio.output.

Attendre la fin du son

Comme la sortie audio joue le son de manière asynchrone, il doit y avoir un moyen d'informer le processus principal de la fin du son. Par "fin du son", j'entends soit la fin réelle du flux audio, soit l'appel de MMM_Stop(). Ainsi, le développeur de l'application n'a pas besoin d'écrire un code séparé pour gérer l'arrêt naturel et l'arrêt forcé de la lecture.

La classe offre deux méthodes pour signaler la fin du son, à savoir que le processus audio peut envoyer un signal ou répondre à un message. L'application précise la méthode choisie et ses paramètres en exécutant l'une des deux méthodes décrites ci-dessous sur l'objet audio.output. Les méthodes sont généralement appelées avant le début de la lecture, mais peuvent également être appelées lorsque l'objet est déjà en cours de lecture. Cette dernière solution est toutefois délicate, car le son peut être très court, et une méthode peut donc être appelée après la fin du son. Dans ce cas, les demandes de signalisation ne seront jamais déclenchées.

La méthode MMM_SignalAtEnd() doit être utilisée lorsque l'on souhaite recevoir un signal qui sera déclenché par Wait(). Elle a deux paramètres, le pointeur du processus à signaler et le numéro du signal (pas le masque !) à envoyer. Nous voulons généralement être signalés nous-mêmes, mais ce n'est pas une obligation, ainsi le processus A peut commencer la lecture, mais le signal peut être reçu par le processus B. Une utilisation typique peut ressembler à ceci :

DoMethod(play, MMM_SignalAtEnd, FindTask(NULL), SIGBREAKB_CTRL_C);
DoMethod(play, MMM_Play);
Wait(SIGBREAKF_CTRL_C);

Dans ce code, nous envoyons une demande pour informer les autres commandes elles-même à l'aide du signal système Ctrl-C. Il peut bien sûr s'agir d'un signal privé alloué. Notez que la méthode MMM_SignalAtEnd() attend un numéro de signal alors que la méthode suivante Wait() a besoin d'un masque de signal.

Voici un exemple complet (NDLR : fichier à présent indisponible) de code source utilisant un signal.

MMM_ReplyMsgAtEnd() signale la fin du son en envoyant un message système préparé par l'application vers un port de message défini par l'application également. Cette méthode est particulièrement utile lorsqu'une application utilise plusieurs sons à la fois. Le nombre de signaux disponibles pour un processus est très limité. Le nombre de messages créés n'est limité que par la mémoire disponible. Cette méthode est également utile si l'application crée un port de message à d'autres fins. Les messages de fin d'audio peuvent alors être dirigés vers ce port et distingués par le contenu du message. Une utilisation typique ressemble à ce qui suit :

struct MsgPort *port; /* créé ailleurs */
struct Message *msg; /* alloué ailleurs */

msg->mn_Node.ln_Type = NT_MESSAGE;
msg->mn_Length = sizeof(struct Message);
msg->mn_ReplyPort = port;

DoMethod(play, MMM_Sound_ReplyMsgAtEnd, msg);
DoMethod(play, MMM_Play);
WaitPort(port);
GetMsg(port);

La principale différence entre ces deux méthodes est que la signalisation des messages se fait en une fois. Après que le message soit envoyé au port de l'application, il doit être récupéré du port et réinitialisé avant de pouvoir être réutilisé à nouveau. La méthode du signal peut être utilisée de manière répétée, ce qui est confortable lorsqu'un son court est déclenché plusieurs fois.

3.5 Tutoriel 5 : lecture d'un son à partir de la mémoire

Bien que la lecture d'un son à partir d'un fichier soit la méthode la plus courante, elle présente plusieurs inconvénients dans certaines applications. Lorsqu'un son est court, qu'il est joué plusieurs fois et qu'une faible latence est requise, la lecture de ce son à partir de la mémoire sera une meilleure option. La recherche dans le fichier audio, ou sa relecture, sera alors beaucoup plus rapide, car elle n'implique aucune activité du disque. D'un autre côté, la lecture depuis la mémoire doit être utilisée avec précaution. Les données audio consomment généralement beaucoup d'espace. Un effet audio de cinq secondes stocké en PCM et qualité CD Audio occupe 861 ko de mémoire. Une solution à ce problème est d'utiliser des formats compressés et de laisser Reggae les décompresser à la volée.

Il y a deux façons de placer un fichier audio en mémoire. Premièrement, il peut être chargé depuis le disque. Deuxièmement, le contenu du fichier audio peut être incorporé dans le fichier exécutable.

Chargement du fichier audio à partir du disque

Dans cette méthode, les sons sont stockés sous forme de fichiers séparés de l'exécutable de l'application. Les tampons mémoire pour les sons sont alloués dynamiquement. Cette approche permet de changer facilement les sons ou, par exemple, de fournir des versions de basse qualité pour les machines ayant moins de mémoire, ou même de fonctionner sans son, lorsque la mémoire est limitée. Le code et la gestion des erreurs sont toutefois un peu plus compliqués. Le processus de mise en mémoire tampon du son n'implique pas du tout Reggae. Le fichier est ouvert et dimensionné, puis le tampon est alloué, le fichier est lu dans le tampon et fermé. Ces tâches peuvent être exécutées par des appels natifs de la bibliothèque dos.library (Open(), Read(), Close()), ou par des appels de la bibliothèque standard C (fopen(), fread(), fclose()). Comme ces derniers ne sont que des adaptateurs pour les appels natifs, il est recommandé d'utiliser les appels natifs, sauf si la portabilité du code est importante. Le code suivant met en mémoire tampon un fichier avec une vérification minimale des erreurs :

BPTR handle;
LONG size;
APTR buffer;
struct FileInfoBlock *fib;

if (fib = AllocDosObject(DOS_FIB, NULL))
{
  if (handle = Open("PROGDIR:sounds/anysound.wav", MODE_OLDFILE))
  {
    if (ExamineFH(handle, fib))
    {
      size = fib->fib_Size;

      if (buffer = AllocVecTaskPooled(size))
      {
        if (Read(handle, buffer, size) == size)
        {
          /* use buffer as memory.stream data here */
        }
        FreeVecTaskPooled(buffer);
      }
    }
    Close(handle);
  }
  FreeDosObject(DOS_FIB, fib);
}

Les développeurs débutant sur MorphOS peuvent remarquer quelques nouveautés dans le code ci-dessus. Bien qu'elles ne soient pas liées à Reggae, elles méritent quelques explications. La première est l'assignation "PROGDIR:" (ou "link" dans la nomenclature Unix). Cela signifie simplement le répertoire où se trouve le fichier exécutable en cours d'exécution. C'est donc un moyen facile de se référer aux données de l'application avec des chemins relatifs. Lorsque l'utilisateur déplace le répertoire de l'application quelque part, les chemins utilisant PROGDIR: sont toujours valides.

La deuxième chose est l'appel AllocVecTaskPooled(). Chaque processus MorphOS a un pool de mémoire automatiquement assigné, qui est éliminé quand le processus se termine. En utilisant ce pool pour les allocations, une application n'a pas besoin de les suivre, car toute la mémoire qui n'est pas libérée explicitement avec FreeVecTaskPooled() sera libérée lorsque le pool de mémoire sera éliminé.

Intégration du son dans l'exécutable de l'application

L'intégration du son dans le code présente l'avantage de la simplicité. L'application est plus autonome. Il n'y a pas d'opérations sur le disque, donc pas besoin de code de gestion des erreurs. D'un autre côté, cela peut rendre l'exécutable très volumineux, et l'utilisateur n'a aucune chance de modifier les sons. Le fichier audio de n'importe quel format peut être converti en code C (sous forme d'un grand tableau) avec l'outil BinToC. La source générée est ajoutée au projet et compilée. Ensuite, l'adresse de la table (dénotée en C comme le nom de la table) et sa longueur en octets, sont passées comme paramètres à l'objet memory.stream (voir le bout de code ci-dessous).

Le flux de mémoire comme source de données

Reggae utilise la classe memory.stream pour accéder aux données situées dans la mémoire du système. Son utilisation est similaire à celle de file.stream, il y a cependant quelques différences. La première est le nom du flux. Pour memory.stream, il s'agit d'une chaîne contenant l'adresse du flux sous la forme d'un nombre hexadécimal, comme "2749FA0C". L'attribut MMA_StreamName n'est cependant pas souvent utilisé. On a généralement l'adresse sous forme de nombre, et non de texte. La convertir en texte juste pour que Reggae la reconvertisse en nombre n'a pas beaucoup de sens. L'attribut MMA_StreamHandle vient alors en aide. Sa valeur est juste l'adresse du flux, passée en tant que nombre. Un autre attribut très important est MMA_StreamLength. Les flux basés sur la mémoire n'ont pas de fin "naturelle". Lorsque l'on lit un fichier, DOS signale simplement la condition EOF (end of file - fin de fichier) lorsque le fichier se termine. En mémoire, on peut lire sans fin, jusqu'à ce qu'on atteigne la fin de l'espace mémoire physique. C'est pourquoi MMA_StreamLenght est un attribut obligatoire pour les flux de mémoire. Reggae refusera de créer un objet stream si cet attribut n'est pas spécifié. Notez également que l'attribut en général est de 64 bits, et prend un pointeur sur un nombre de 64 bits. Passer juste un nombre de 32 bits comme valeur est une erreur commune ici. L'extrait de code ci-dessous montre la création typique d'un objet de flux de mémoire à partir d'un son intégré dans un fichier exécutable :

CONST UBYTE SoundData [12837] = { /* ce sont les déonnées audio */ }; /* La longueur est donnée à titre d'exemple. */
QUAD length = 12837;
Object *sound;

sound = MediaNewObject(
  MMA_StreamType, "memory.stream",
  MMA_StreamHandle, SoundData,
  MMA_StreamLength, &length,
TAG_END);

Lorsque le son est mis en mémoire tampon à partir d'un fichier, il faut d'abord vérifier la taille du fichier, puis allouer un tampon et y charger le fichier à l'aide des appels habituels de la dos.library, ou des appels de la bibliothèque standard C. Le processus est illustré dans le code source complet de l'exemple. Une fois le tampon chargé, l'objet memory.stream est créé de la même manière que ci-dessus.

Lecture et contrôle de la lecture

Pour lire le son, on connecte l'objet média créé avec un objet audio.output, exactement comme pour la lecture depuis un disque. Il n'y a pas non plus de différence pour contrôler la lecture, ou pour attendre la fin du son. Grâce à l'abstraction de flux, Reggae ne se soucie pas de la nature du flux. Les opérations de recherche et de redémarrage sont beaucoup plus rapides. C'est important pour les effets sonores courts joués plusieurs fois (pensez au son d'un tir dans un jeu). L'exemple de code ci-dessus permet à l'utilisateur de rejouer le son en appuyant sur la touche "Entrée". Cela peut être fait très rapidement sans délai, en supposant qu'une simple compression audio soit utilisée (il suffit d'appuyer et de maintenir "Entrée", la vitesse de redémarrage sera aussi rapide que la vitesse de répétition des touches définie dans les préférences du système).

L'exemple montre aussi la stratégie "lancer et oublier" de la lecture des sons avec Reggae. Il n'y a pas de vérification de la fin du son. MMM_Stop() s'arrête simplement et cherche à revenir au début. Ensuite, MMM_Play() commence la lecture. Peu importe si le redémarrage se produit alors que le son précédent est toujours en cours de lecture ou non. Il n'y a pas non plus de vérification de la fin du son lorsque l'utilisateur arrête le programme. L'élimination d'un objet audio.output actif (en cours de lecture) est parfaitement sûre.

3.6 Tutoriel 6 : lecture d'une onde continue, synthétisée

Ce tutoriel montre comment des données sonores brutes peuvent être jouées avec Reggae. L'exemple de code (NDLR : fichier à présent indisponible) synthétise une onde sinusoïdale de 1 kHz et la joue en continu. L'onde est synthétisée dans un tableau en mémoire. Ensuite, memory.stream est utilisé pour y accéder. L'objet suivant, une instance de rawaudio.filter, attache des paramètres audio aux données brutes. Enfin, audio.output lit l'onde dans une boucle sans fin, en utilisant sa fonction de bouclage.

Synthèse d'une onde sinusoïdale brute

En théorie, une seule période de l'onde sinusoïdale est suffisante pour la jouer en continu. Il y a cependant deux raisons d'utiliser plus de périodes. La première est le nombre d'échantillons dans une période. Nous voulons jouer une onde de 1 kHz échantillonnée à 44,1 kHz. Une période contiendrait alors 44,1 échantillons, ce qui n'est évidemment pas un nombre entier. La deuxième raison est la surcharge de traitement de la boucle serrée. Chaque tour de boucle prend du temps de traitement supplémentaire, il est donc préférable que la boucle soit plus longue.

En tenant compte de ces deux raisons, 100 périodes du sinus sont générées dans la table contenant 4410 échantillons, ce qui donne 0,1 seconde de son. La génération est alors simple et utilise la fonction sin() de la bibliothèque mathématique C standard. Comme l'argument de sin() est en radians (donc une période vaut 2π), elle va de 0 à 200π en 4410 pas (sans la dernière valeur). L'amplitude de l'onde sinusoïdale (qui est normalement de 1,0) est mise à l'échelle dans une plage signée de 16 bits en la multipliant par 32 767.

Utilisation de memory.stream comme tampon de synthèse

L'utilisation de base de memory.stream a été abordée dans le tutoriel "Lecture d'un son à partir de la mémoire". L'attribut MMA_StreamHandle est utilisé pour transmettre l'adresse de la table mémoire, l'attribut MMA_StreamLength prend la taille de la table en octets (attention, ce n'est pas la même que la taille de la table dans la déclaration, car chaque élément de la table occupe deux octets). Notez également que MMA_StreamLength est un attribut 64 bits, et en tant que tel, il est transmis comme un pointeur vers une variable QUAD contenant la valeur.

Application des paramètres audio avec rawaudio.filter

Alors que la génération de l'onde sinusoïdale est effectuée, la sortie memory.stream ne peut pas être connectée directement à l'objet audio.output. Ceci est dû au fait que les classes stream ne fournissent qu'un simple flux d'octets sans aucune signification assignée. Dans le tutoriel précédent, Reggae était capable de lire ce flux, car il était auto-descripteur (contenait un en-tête, par exemple AIFF ou WAVE). Les données brutes ne sont pas auto-descriptives, nous devons donc les décrire nous-mêmes à Reggae. Une "conversion" du flux en signal audio est faite avec l'objet rawaudio.filter. L'utilisation des guillemets est intentionnelle. En fait, cette classe ne convertit pas les données, elle attache simplement le format audio et les attributs au flux de données. Le flux est alors reconnu par Reggae comme étant de l'audio et peut être traité ultérieurement, ce qui inclut bien sûr sa lecture avec audio.output.

L'application des informations audio se compose de deux parties. Les attributs audio (nombre de canaux, fréquence d'échantillonnage et volume) sont définis lors de la création de l'objet rawaudio.filter. Le format des échantillons est défini en le fixant sur le port de sortie de l'objet créé :

Object *rawaudio;

rawaudio = NewObject(NULL, "rawaudio.filter",
  MMA_Sound_Channels, 1,
  MMA_Sound_SampleRate, 44100,
  MMA_Sound_Volume, 65536,
TAG_END);

MediaSetPort(rawaudio, 1, MMA_Port_Format, MMFC_AUDIO_INT16);

Une grande partie du code ci-dessus est redondante, car il se trouve que les valeurs définies correspondent aux valeurs par défaut de rawaudio.filter. A savoir, le nombre de canaux par défaut est 1, le volume par défaut est 65536 ($10000 = plein volume) et le format d'échantillonnage par défaut est un entier de 16 bits. La fréquence d'échantillonnage par défaut diffère cependant, puisqu'elle est de 8000 Hz. Quoi qu'il en soit, tous les attributs ont été définis dans l'exemple de code, juste pour être complet.

On peut utiliser n'importe quel des trois formats audio de Reggae pour les données brutes. A l'exception des entiers de 16 bits, Reggae gère les entiers signés de 32 bits (MMFC_AUDIO_INT32) et les flottants de 32 bits de précision (MMFC_AUDIO_FLOAT32). Leur utilisation pour la lecture n'a cependant pas beaucoup de sens, car les données seront de toute façon converties en 16 bits.

Rawaudio.filter permet également d'utiliser tous les formats gérés par audiopcm.decoder et laws.decoder. Cela inclut le PCM 8/16/24/32 bits dans les deux boutistes (les données 8 bits peuvent être signées ou non), les flottants 32 bits dans les deux boutistes et les données 8 bits quantifiées de manière non linéaire selon la loi A ou µ. Dans ce cas, un objet décodeur approprié doit être créé et connecté à la sortie du filtre. Cette fonctionnalité peut être utile pour charger des données brutes à partir de fichiers créés par des applications externes. Pour les données générées en interne, l'utilisation des formats standards Reggae est recommandée (cela évite des conversions supplémentaires).

Boucler le son pour le rendre continu

C'est la partie la plus facile. La fonction de bouclage est intégrée à la classe audio.output. Cette fonction est contrôlée par l'attribut MMA_Sound_LoopedPlay. Lorsqu'il est défini à TRUE, la sortie audio, après avoir rencontré la fin du son, cherche le début du flux et continue la lecture. Grâce au double tampon, il n'y a pas d'interruption de la lecture, à condition que la recherche soit suffisamment rapide (ce qui est vrai pour les flux de mémoire et les flux de fichiers sur les supports de stockage locaux). Ensuite, dans le cas de notre onde sinusoïdale, la mise en boucle est transparente. Comme mentionné ci-dessus, la boucle ne doit pas être trop courte. Pour les flux mémoire, 0,1 seconde est suffisante, pour les flux sur disque, 0,5 seconde serait sans danger.

L'attribut MMA_Sound_LoopedPlay peut être transmis à NewObject() ou défini ultérieurement avec SetAttrs(), ou MediaSetPort() sur le port d'entrée de l'instance audio.output. Le code de l'exemple utilise la deuxième approche.

3.7 Tutoriel 7 : enregistrement de l'audio dans le format choisi par l'utilisateur

Outre le décodage automatique des médias, Reggae dispose également d'une fonction d'encodage automatique. Il existe toutefois une différence fondamentale entre ces deux fonctions. Dans le cas du décodage, le format du média décodé, les paramètres du codec, les paramètres du flux, les métadonnées, tout cela provient du flux de données décodé. Lorsque les médias sont codés, le format, les paramètres du codec et les métadonnées doivent être définis par l'application.

En général, une application veut offrir tous les formats disponibles à l'utilisateur. Cela signifie que l'auteur de l'application doit maintenir une interface graphique pour tous les codecs et leurs paramètres. De plus, une telle interface graphique devrait être mise à jour avec chaque nouveau codec publié. Reggae change cela et facilite la vie du développeur d'application. La règle principale est que les classes de multiplexeurs et d'encodeurs fournissent des interfaces graphiques. Reggae rassemble ces interfaces dans un seul objet et le renvoie à l'application. L'application peut alors intégrer cet objet composé d'interface graphique dans sa propre interface.

L'utilisateur peut utiliser cet objet pour sélectionner l'encodeur et ses paramètres. Mais ce n'est pas tout. Après que l'utilisateur ait sélectionné le format de sortie et ses paramètres, Reggae peut créer une paire encodeur-mélangeur, lire les paramètres et les métadonnées de l'interface graphique, et les définir dans les objets appropriés. L'application connecte ensuite la source de données à l'entrée et la classe de flux de sortie à la sortie. Après avoir déclenché le traitement avec la méthode MMM_Play() sur l'objet de flux de sortie, les données audio sont écrites.

Grâce à l'API de sauvegarde des médias de Reggae ("Reggae saver"), le développeur d'application n'a pas à se soucier des formats gérés par Reggae. En outre, même si de nouveaux codecs sont publiés après la sortie de l'application, celle-ci les utilisera sans qu'il soit nécessaire de la mettre à jour.

Préparation des données source

Les données audio source peuvent être préparées soit sous forme de tampon statique, soit générées à la demande. La technique du tampon statique est plus simple et sera décrite ici. La génération en temps réel peut être réalisée en écrivant une classe Reggae personnalisée (voir le code source de U1Synth), ou en utilisant la classe datapush.stream. Les données sources peuvent également provenir de Reggae, par exemple d'un fichier audio décodé. C'est le cas d'un convertisseur audio Zormanita, dont le code source est également disponible.

Les données dans un tampon doivent être dans l'un des formats communs de Reggae. Il existe trois formats communs pour l'audio : INT16, INT32 et FLOAT32. Le choix dépend de l'objectif et de la qualité de traitement requise. INT16 est le format le plus rapide et est le mieux adapté à la lecture. FLOAT32 a plus ou moins une résolution de 24 bits et peut être pratique pour de nombreux algorithmes de synthèse, qui sont souvent plus faciles à mettre en oeuvre avec des mathématiques à virgule flottante. INT32 est le plus lent, mais fournit la meilleure qualité, généralement supérieure aux étapes de traitement ultérieures. Les trois formats utilisent des nombres signés avec l'ordre des octets natif de l'hôte (qui est gros-boutiste pour PowerPC). La plage des nombres à virgule flottante est également [-1.0 ; +1.0]. Bien qu'elle puisse être temporairement dépassée, toute classe Reggae est autorisée à découper les données dans cette plage. Si l'audio est multicanal, les canaux sont entrelacés (pour la stéréo, l'ordre est L, R).

L'adresse de départ du tampon doit être alignée sur l'AltiVec. Bien que ce ne soit pas une obligation pour les données récupérées via l'objet memory.stream (nous y reviendrons plus tard), cela peut accélérer un peu le traitement. La façon la plus simple d'obtenir l'alignement est d'allouer le tampon avec la fonction MediaAllocVec() de la classe multimedia.class. D'autre part, il n'est pas nécessaire d'aligner la taille du tampon sur celle d'une seule trame audio (ensemble d'échantillons de tous les canaux pour un point temporel). Bien sûr, le remplissage du tampon avec des données dépend de l'application. Dans l'exemple de code ci-dessous, un tampon est créé avec 1 seconde d'audio monophonique échantillonné à 44,1 kHz. Il contient 44 100 échantillons audio INT16, la taille du tampon est donc de 88 200 octets.

WORD *Buffer;

Buffer = (WORD*)MediaAllocVec(88200);

Le tampon est ensuite rempli de son. A titre d'exemple, il est montré comment il peut être rempli par une onde sinusoïdale de 1 kHz.

LONG i;

for (i = 0; i < 44100; i++) Buffer[i] = (WORD)(sin(2000 * M_PI * i / 44100) * 32767.0);

Le code n'est absolument pas optimisé, mais il a été laissé tel quel pour être facilement lisible.

Création de l'interface graphique de sélection de format

Créer un objet MUI contenant un sélectionneur de format et des interfaces graphiques pour tous les encodeurs est très facile. Il suffit d'appeler la fonction MediaGetGuiTagList(). Comme son nom l'indique, une liste de balises peut être utilisée pour contrôler la création de l'interface graphique. En voici un exemple :

Object *FormatSelector;

FormatSelector = MediaGetGuiTags(
  MGG_Type, MGG_Type_Muxers,
  MGG_Media, MMT_SOUND,
  MGG_Selector, MGG_Selector_List,
  MUIA_Frame, MUIV_Frame_Group,
  MUIA_Background, MUII_GroupBack,
TAG_END);

Abordons à présent le sujet des balises. La première, MMG_Type, sélectionne le type de classes Reggae interrogées pour l'interface graphique. Il y a actuellement deux possibilités. MGG_Type_Muxers est utilisé lorsqu'on encode et enregistre le flux média. Il y a aussi MGG_Type_Filters, qui peut être utilisé pour créer un sélectionneur de filtre Reggae pour le traitement des médias. La seconde sera couverte dans d'autres articles. La balise suivante, MGG_Media, définit le type de média sauvegardé. Lorsque l'on a des données audio, cela n'a pas de sens d'afficher des formats image ou vidéo. La valeur MMT_SOUND permet de s'assurer que seuls les codecs audio sont affichés. La troisième balise, MGG_Selector, influence l'apparence visuelle de l'ensemble de l'objet d'interface graphique. Les interfaces graphiques fournies par les codecs sont placées comme des pages d'un groupe de pages MUI. Il faut alors un gadget pour faire défiler ces pages. Il peut s'agir d'un gadget de type liste ou cycle, comme illustré ci-dessous :

Reggae Reggae

Le formulaire présenté à gauche utilise un gadget de cycle pour la sélection du format. Ce style est plus compact. D'autre part, la sélection de format par liste a l'avantage de montrer immédiatement le choix de formats. Dans le cas du gadget de cycle, l'utilisateur doit cliquer dessus pour obtenir un menu contextuel. Le sélectionneur de cycle n'est recommandé que dans le cas où l'espace pour l'interface graphique est limité (par exemple dans les requêtes de fichiers personnalisées).

L'objet retourné est une sous-classe de la classe MUI Group. En tant que tel, il peut se voir attribuer un cadre et un arrière-plan si nécessaire. Dans l'exemple de code, il a le cadre et l'arrière-plan standard du groupe (le cadre n'est pas montré sur les images ci-dessus). L'objet est généralement ajouté à l'interface graphique de l'application en tant que valeur d'une balise MUIA_Group_Child. Dans la plupart des cas, il est créé statiquement à l'initialisation de l'application. Zormanita et U1Synth créent cet objet de manière statique. Il peut aussi être créé dynamiquement, par exemple comme élément d'une fenêtre dynamique. Toutes les techniques typiques de MUI s'appliquent.

Construction de la chaîne de traitement du Reggae

1. Tampon de mémoire comme source de données

Les données contenues dans une mémoire tampon nécessitent un certain travail pour être utilisées comme source de Reggae. Il faut deux objets Reggae pour les gérer. Le premier objet est une instance de la classe memory.stream. A sa sortie, Reggae voit un flux d'octets, sans autre signification ou structure. Nous devons alors indiquer à Reggae qu'il ne s'agit pas seulement d'un flux d'octets, mais d'un flux d'échantillons audio avec un type, un nombre de canaux, un taux d'échantillonnage, etc. Ceci est fait avec l'objet rawaudio.filter.

Bien que cela semble inutilement compliqué, cela permet une plus grande liberté. En remplaçant memory.stream par file.stream, on peut récupérer des données audio à partir d'une mémoire de masse, sans être limité par la mémoire disponible. Il convient également de noter que memory.stream et rawaudio.filter ne sont que des adaptateurs. Ils n'introduisent aucune surcharge de traitement ni aucune copie de données inutile. Le flux rawaudio utilisant un tampon en mémoire est créé comme suit :

Object *stream;
QUAD stream_length = 88200;

stream = NewObject(NULL, "memory.stream",
  MMA_Stream_Handle, (IPTR)Buffer,
  MMA_Stream_Length, (IPTR)&stream_length,
TAG_END);

Comme l'attribut MMA_Stream_Length prend un nombre QUAD, il doit être transmis via un pointeur. La longueur doit être spécifiée pour memory.stream, car il n'a pas de fin naturelle, par exemple file.stream. Bien sûr, la classe memory.stream doit être ouverte au préalable avec OpenLibrary(), comme décrit dans le paragraphe "Ouverture et fermeture de classes individuelles".

L'étape suivante est rawaudio.filter. Sa création est assez simple :

Object *raw_audio;

raw_audio = NewObject(NULL, "rawaudio.filter",
 MMA_Sound_Channels, 1,
 MMA_Sound_SampleRate, 44100,
TAG_END);

MediaSetPort(raw_audio, 1, MMA_Port_Format, MMFC_AUDIO_INT16);

Notez que le format audio n'est pas spécifié en tant que balise pour le constructeur de l'objet, mais est juste défini pour le port de sortie. Comme dernière étape de cette section, nous connectons ces deux objets ensemble :

MediaConnectTagList(stream, 0, raw_audio, 0, NULL);

La sortie du flux est maintenant connectée à l'entrée rawaudio.filter.

2. Encodeur, multiplexeur et leur configuration

Cette étape est réalisée automatiquement par Reggae. Pour configurer le duo encodeur et multiplexeur, Reggae a besoin de deux sources d'information :
  • L'objet GUI avec toutes les sélections faites par l'utilisateur.
  • Le flux de données source.
Les deux sont fournis comme arguments à la fonction MediaBuildFromGuiTags() :

Object *codec;

codec = MediaBuildFromGuiTags(format_selector, raw_audio, 1, TAG_END);

Cette fonction effectue une tâche complexe de configuration du codec multimédia. Analysons-la étape par étape :
  • Reggae vérifie dans l'interface graphique quel format a été sélectionné par l'utilisateur. Il ouvre la classe de multiplexeur appropriée et crée son instance.
  • Sur la base des sélections détaillées de l'interface graphique, Reggae détermine quel encodeur coopérera avec le multiplexeur sélectionné précédemment. Sa classe est à nouveau ouverte et l'objet est créé.
  • L'encodeur est connecté à la source de données passée en arguments (objet et port). Les paramètres de l'encodeur sont définis en fonction de l'interface graphique et des paramètres de la source de données.
  • Le multiplexeur est connecté à l'encodeur. Là encore, les paramètres du multiplexeur (le cas échéant) sont définis en fonction de l'interface graphique et des paramètres du flux.
  • Les deux objets (plus tout objet auxiliaire que Reggae peut créer silencieusement) sont regroupés dans un seul objet composé et retournés comme tels.
  • A la fin de cette étape, nous avons une chaîne de traitement Reggae presque complète et connectée pour sauvegarder l'audio. Le port de sortie du codec n'est pas connecté, il attend l'écriture de l'objet stream.
Sortie des données

La dernière chose dont nous avons besoin est un objet qui écrit les données encodées. Dans la plupart des cas, il s'agit de l'instance file.output. On la crée avec le code suivant :

Object *writer;

writer = NewObject(NULL, "file.output",
  MMA_StreamName, (IPTR)"somefile",
TAG_END);

Dans cet exemple, le nom du fichier est codé en dur, ce qui est bien sûr de mauvais goût. Pour les programmes Shell, le nom du fichier provient des arguments de la ligne de commande, pour les programmes avec une interface graphique, il provient d'une requête de fichiers, ou d'un objet Popasl MUI. Bien sûr, la classe file.output accepte les chemins complets, à la fois absolus (commençant par le nom du volume) et relatifs au répertoire actuel du programme. Après la création, l'objet de sortie est connecté à la sortie du codec :

MediaConnectTagList(codec, 1, writer, 0, NULL);

Maintenant, toute la chaîne est prête à sauvegarder les données. Il ne nous reste plus qu'à actionner un déclencheur...

Sauvegarde de l'audio

Un objet de la classe file.output crée un sous-processus. Tout le travail lié à l'encodage du média et à son écriture sur le disque est effectué par ce sous-processus. Pendant l'écriture, l'application n'est pas bloquée. C'est pourquoi il peut être judicieux de désactiver les gadgets qui déclenchent la sauvegarde des médias pendant la durée de l'écriture.

Comme la sauvegarde est asynchrone, il doit y avoir un moyen de notifier l'application lorsqu'elle est terminée. Cela se fait exactement de la même manière que pour la lecture audio, on utilise même les mêmes méthodes. On peut alors obtenir un signal ou une réponse pour un message, lorsque la sauvegarde est terminée. L'attente d'un tel signal ou message peut être ajoutée à la boucle d'événements MUI.

La notification de la fin de la sauvegarde est généralement définie avant de la lancer. Un code minimaliste dans une application Shell peut ressembler à ceci :

DoMethod(writer, MMM_SignalAtEnd, (IPTR)FindTask(NULL), SIGBREAKB_CTRL_C);
DoMethod(writer, MMM_Play);
Wait(SIGBREAKF_CTRL_C);

Notez que si Wait() prend un masque de signal, MMM_SignalAtEnd() prend un numéro de signal. Ne les confondez pas. Bien sûr, un tel code bloque l'application pour gagner du temps. Plus avancé, on peut par exemple vérifier périodiquement la progression et afficher des messages. Par exemple, Zormanita met à jour la barre de progression pendant la sauvegarde.

Bien que la sauvegarde du média puisse être interrompue à tout moment en exécutant la méthode MMM_Stop() sur l'objet qui écrit, cela n'est pas recommandé. Actuellement, elle n'est sûre que pour le format brut (rawaudio.muxer). Les multiplexeurs AIFF et WAVE écrivent les en-têtes au début et ne les mettent pas à jour lorsque l'écriture est arrêtée. L'arrêt de l'écriture laisse donc des fichiers enregistrés incomplets, avec des valeurs d'en-tête ne correspondant pas aux données réellement stockées. Ce comportement peut ou non être amélioré à l'avenir. Il appartient alors à l'application de supprimer ces fichiers incomplets (cela inclut également les cas où l'écriture est interrompue en raison d'une erreur d'entrées/sorties).

Nettoyage

Pendant le nettoyage, l'application doit fermer toutes les classes qu'elle a créées et se débarrasser de tous les objets. Il y a cependant quelques exceptions. Une fois que l'objet MUI retourné par MediaGetGuiTags() est ajouté à une application MUI, il n'est pas nécessaire de le détruire séparément. Il sera automatiquement éliminé lorsque l'objet MUI Application sera éliminé.

Les objets Reggae doivent être éliminés dans l'ordre de la sortie vers la source :

DisposeObject(writer);
DisposeObject(codec);
DisposeObject(raw_audio);
DisposeObject(stream);

Cet ordre est sûr même si l'objet auteur est en cours d'exécution au moment où il est éliminé. Si l'ordre n'est pas maintenu et que la chaîne en cours d'exécution est éliminée, il est théoriquement possible que Reggae accède à de la mémoire déjà libérée.

Les classes Reggae utilisées doivent également être fermées, à l'exception des classes pour le multiplexeur et l'encodeur. Comme ces classes sont automatiquement ouvertes par Reggae, elles sont aussi automatiquement fermées. Ainsi, dans l'exemple, seules les classes memory.stream, rawaudio.filter et file.output doivent être fermées par l'application. En résumé, ce qui est explicitement ouvert/créé par l'application doit être explicitement fermé/jeté. Tout ce qui est créé automatiquement par Reggae est géré par Reggae. Enfin, tout ce qui est créé par MUI est éliminé automatiquement par MUI.

Contrôle de la sauvegarde sans interface graphique

Bien que l'objet GUI fourni par Reggae pour choisir l'encodeur réponde à la plupart des besoins des applications, il existe des cas où un contrôle de plus bas niveau est nécessaire. Quelques exemples incluent notamment :
  • Commandes du Shell.
  • Interface de script.
  • Journal des applications.
  • Applications utilisant un format de sortie fixe.
Ce contrôle de niveau inférieur est implémenté comme une chaîne d'arguments. Cette chaîne contient la spécification du format audio sous forme textuelle, lisible par l'homme, très similaire à la forme des arguments des commandes du Shell MorphOS. En fait, la chaîne d'arguments Reggae est analysée avec la même fonction que les arguments du Shell, à savoir ReadArgs() de la dos.library. Pour en dire plus, même lorsque l'application construit une chaîne de sauvegarde Reggae à partir de l'interface graphique, la chaîne d'arguments est utilisée en interne comme une étape intermédiaire.

Reggae

Comment construire une sauvegarde Reggae à partir d'une chaîne d'arguments ? Il existe une fonction dans multimedia.class, appelée MediaBuildFromArgsTags(). Elle fonctionne exactement de la même manière que MediaBuildFromGuiTags(), la seule différence est qu'elle prend une chaîne d'arguments au lieu d'un objet GUI. Mais à quoi ressemble la chaîne d'arguments ? Prenons un exemple simple :

WAVE BITS=24

Chaque spécification d'une sauvegarde Reggae se compose de deux parties. La première partie est le nom de la classe du multiplexeur, sans l'extension ".muxer". Il est mis en majuscule ici, mais les chaînes d'arguments Reggae sont insensibles à la casse. La deuxième partie est le reste de la chaîne (après avoir sauté les espaces après le nom du multiplexeur), elle peut donc contenir plusieurs arguments. Dans certains cas, la deuxième partie peut être vide, car la plupart des formats ont des valeurs par défaut pour les paramètres de l'encodeur. Par exemple, on peut utiliser simplement :

AIFF

...pour que les données soient stockées dans un fichier AIFF 16 bits. Bien sûr, chaque multiplexeur a son propre jeu de paramètres, décrit dans son autodoc.

Les applications qui souhaitent enregistrer les opérations effectuées avec l'interface graphique, ou créer un historique d'annulation, peuvent également récupérer la chaîne d'arguments de l'objet GUI avec MediaGetArgsFromGui(). Elle générera la chaîne d'arguments en se basant sur les états actuels des gadgets.

Par rapport à l'interface graphique, l'interface textuelle des sauvegardes de Reggae présente deux inconvénients :
  • L'utilisateur (ou l'application, si la chaîne d'argument n'est pas donnée par l'utilisateur) doit savoir quels multiplexeurs sont installés dans le système et doit connaître les paramètres de l'encodeur.
  • L'interface graphique est conçue de manière à ne pas permettre à l'utilisateur d'entrer des combinaisons de paramètres insensées ou conflictuelles (par exemple, l'interface graphique de rawaudio.muxer désactive automatiquement le gadget "Byte Order", lorsque des échantillons de 8 bits sont sélectionnés). Pour la définition textuelle, la validation des paramètres est faite quand la chaîne Reggae est construite, et MediaBuildFromArgsTags() échoue simplement quand les arguments n'ont pas de sens.
Encore un autre exemple

Le troisième exemple d'application utilisant l'API de sauvegarde de Reggae est Zgrzytor. Contrairement à Zormanita et U1Synth, il s'agit d'une commande Shell utilisant la description textuelle de la sauvegarde expliquée dans la section précédente. Zgrzytor est un simple générateur de bruit utilisant un registre à décalage avec retour EXOR. C'est une technique bien connue pour générer des sons périodiques (pour un registre court) ou du bruit pseudo-aléatoire (pour un registre long). L'utilisateur peut spécifier les points (bits) du registre à décalage où est connectée la porte EXOR qui renvoie le registre. Le taux d'échantillonnage de la sortie, le nom du fichier de sortie et le format de sortie peuvent également être spécifiés. Quelques exemples d'appels de Zgrzytor peuvent être :

Zgrzytor 3 11 22050 somenoise.wav WAVE BITS=32

Cela attachera EXOR aux bits 3 et 11 (les bits sont comptés à partir du côté alimentation et à partir de 1) du registre à décalage et générera 5 secondes (le temps est codé en dur dans le programme) de bruit, réglera la fréquence d'échantillonnage à 22,05 kHz et sauvegardera comme WAVE 32 bits (Zgrzytor travaille avec des entiers 32 bits, il peut donc produire du "vrai" son 32 bits). La dernière partie de l'appel, en gras, est juste la chaîne d'arguments de la sauvegarde Reggae. Plus d'informations sur Zgrzytor peuvent être trouvées sur sa page Internet. Bien sûr, le code source complet de Zgrzytor est ajouté à l'archive.


[Retour en haut] / [Retour aux articles]