Suivez-nous sur X
|
|
|
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
|
|
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
|
|
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
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : MUI - gestion CDDA
(Article écrit par Mathias Parnaudeau - août 2004)
|
|
Chapitre 6
Après être entré dans les profondeurs de MUI, on s'en écarte un peu pour une partie cruciale de notre lecteur, à savoir le
module de gestion des CD. Toujours dans un soucis d'apprendre à bien programmer (en MUI ou pas), on exclura toute
fonction d'accès aux disques de notre source lié à l'interface. Cette modularité est indispensable pour que les sources
restent lisibles et cohérents. Ainsi, même si on utilise plusieurs API différentes (suivant le système d'exploitation par exemple) pour
accéder aux disques, l'interface graphique n'a pas à s'en soucier.
Module CDDA
Nous envisageons donc un module que nous appellerons "cdmanager". C'est à lui que notre interface va s'adresser pour
lui demander si un disque est présent, combien de titres se trouvent dessus, etc. Nous n'implémenterons que les
fonctionnalités de base car bien qu'indispensable, ce module ne doit pas nous détourner de notre centre d'intérêt
du moment : MUI.
Pour la lecture de CD, la cdda.library vient simplifier nos affaires sur MorphOS. Sur AmigaOS, il existe la cdplay.library
qui est moins évoluée mais doit offrir des fonctionnalités de base équivalentes. Sinon les lecteurs avaient l'habitude
de piloter directement le lecteur avec des commandes SCSI envoyées au périphérique logique approprié. Pour rester simple, sous
AmigaOS, nous ne pourrons que simuler la présence d'un CD en renvoyant des valeurs fixes. C'est dommage mais dans
le fond ça ne gênera pas au niveau de l'interface. Libre à vous d'implémenter un réel gestionnaire de CD avec
des accès bas niveau !
De quelles fonctions avons-nous besoin ?
Nous devons pouvoir :
- Initialiser et libérer le module.
- Réserver le lecteur pour notre usage personnel et annuler ce verrou.
- Récupérer le nombre de pistes du CD courant.
- Jouer une piste en particulier et arrêter la lecture.
Malheureusement, d'autres fonctionnalités ne seront pas gérées : pause, éjection, etc. il est toujours envisageable
de les implémenter par la suite. L'important pour nous tient dans la réalisation d'un module indépendant auquel
on peut accéder simplement depuis notre interface. De même, la gestion interne du module ne présente peut-être
pas toutes les garanties nécessaires, bien qu'il soit fonctionnel.
Voici les prototypes des fonctions correspondants à celles énumérées ci-dessus :
int CDM_Initialize(STRPTR device, int unit);
void CDM_DeInitialize(void);
int CDM_GetNumTracks(void);
void CDM_PlayTrack(int num);
void CDM_StopTrack(void);
Le préfixe CDM signifie "CD Manager", soit le nom de notre module. Les noms des fonctions sont en anglais, il s'agit
d'une convention personnelle. Seules deux fonctions prennent des arguments : l'initialisation qui indique le périphérique logique
et l'unité choisis, la lecture d'une piste donnée (de 1 à GetNumTracks()).
Entre boutons et actions
Les détails du module CDM restent à votre entière disposition dans le source, nous ne nous attardons pas dessus.
En revanche, voyons l'intégration du module dans l'interface.
L'initialisation de CDM a lieu dès le démarrage de l'application. Le périphérique logique et son unité sont passés en dur, ce qui a
pour conséquence de rendre actuellement inutile l'onglet de configuration. Mais pas de panique, tout est en place
pour remédier à ça. D'autres part, les deux valeurs sont présentées dans une structure appelée Config que l'on
renseigne donc manuellement pour l'instant mais là encore, il suffit d'imaginer une fonction de lecture d'un fichier
de configuration au démarrage pour rendre tout cela beaucoup plus attrayant. Prévoyez aussi la sauvegarde. ;-)
Les fonctions de verrouillage (lock) sont utilisées uniquement en interne. Le module doit s'assurer qu'il a un accès
privilégié au lecteur et au disque pour y lire des informations. Une des premières données que l'on va lire pour
en rendre compte à l'utilisateur concerne le nombre de pistes du CD présent. La valeur obtenue permet d'enrichir
la liste avec autant de nouvelles entrées qu'il n'y a de pistes sur le CD, grâce à la méthode MUIM_List_InsertSingle
de l'objet liste. Il semble que ce dernier ne copie pas dans ses données personnelles les chaînes insérées, c'est
pourquoi nous avons déclaré un tableau (de 24 lignes maximum) pour y conserver les chaînes sur lesquelles pointe l'objet liste.
Voilà pour les opérations qui ont lieu une seule fois. Maintenant, le temps est venu de gérer l'interaction des boutons
et de la liste avec le module CDM. Rappelons les actions de base :
- La lecture d'une piste est commandée quand on clique sur une des entrées de la liste, sur les boutons "Jouer",
"Précédent" et "Suivant". Ceci implique la récupération du numéro de piste à jouer, la mise en action du composant Busy
et l'appel à la lecture effective de la piste.
- L'arrêt de la lecture, provoquée par un clic sur "Arrêter" ou par la fermeture de l'application (dans ce cas, c'est
l'appel à la fonction de libération des ressources qui s'en charge).
Interactions en pratique
Si les actions sur le module CDM étaient gérées par un composant MUI, on pourrait uniquement (et simplement !)
utiliser le mécanisme de notification. En attendant, nous devons ajouter deux hooks :
- Un pour la lecture, quel que soit le composant qui est à l'origine de cette action : il récupère la piste
courante de la liste, commande sa lecture à CDM et active le composant Busy.
- L'autre pour l'arrêt.
Quelques changements concernant le composant Busy ont été apportés. Il n'est plus lié aux boutons Jouer et Arrêter
mais à l'action correspondante, ce qui est plus intéressant ! En effet, le contrôle centralisé en un seul endroit,
dans le hook. La démarche est la suivante : un clic sur le bouton Jouer définit l'entrée active de la liste... action sur
laquelle repose le hook ! Voilà un joli travail d'équipe.
Pour un degré de finition plus avancé, il faudrait récupérer l'événement comme quoi le morceau est fini et passer à
la piste suivante si on n'est pas déjà sur la dernière. Le changement de l'entrée active de la liste entraînerait sa
lecture (grâce au hook_PlayTrack).
Voici le source :
/*
* DisKo6.c (04/08/04)
*
*/
#include <stdio.h>
#include <string.h>
#include <libraries/mui.h>
#include <proto/muimaster.h>
#include <proto/intuition.h>
#include <proto/exec.h>
#include <clib/alib_protos.h>
#include <mui/Busy_mcc.h>
#include <SDI_hook.h>
#include "cdmanager.h"
#define MAKE_ID(a,b,c,d) ((ULONG) (a)<<24 | (ULONG) (b)<<16 | (ULONG) (c)<<8 | (ULONG) (d))
struct Library *MUIMasterBase = NULL;
struct IntuitionBase *IntuitionBase = NULL;
#ifdef __amigaos4__
struct IntuitionIFace *IIntuition;
struct MUIMasterIFace *IMUIMaster;
#endif
#define MUIA_Application_UsedClasses 0x8042e9a7 /* V20 STRPTR * i.. */
static char *ClassList[] =
{
"Busy.mcc",
NULL
};
static Object *app = NULL;
static Object *window = NULL;
static Object *list;
static Object *busy;
static char *Pages[] = { "Utilisation", "Configuration", NULL };
struct Config {
int unit;
char device[32];
} config;
static char titles[24][64];
/*
* Hooks nécessaires en attendant mieux ... ;-)
*/
HOOKPROTONH(ChangeVolume, ULONG, APTR obj, struct TagItem *tag_list)
{
int val;
get(obj, MUIA_Numeric_Value, &val);
printf("Nouvelle valeur : %d\n", val);
return TRUE;
}
MakeStaticHook(hook_ChangeVolume, ChangeVolume);
HOOKPROTONH(hook_PlayTrackFunc, ULONG, APTR obj, struct TagItem *tag_list)
{
int val;
get(obj, MUIA_List_Active, &val);
val++;
CDM_PlayTrack(val);
set(busy, MUIA_Busy_Speed, 20);
return TRUE;
}
MakeStaticHook(hook_PlayTrack, hook_PlayTrackFunc);
HOOKPROTONH(hook_StopTrackFunc, ULONG, APTR obj, struct TagItem *tag_list)
{
CDM_StopTrack();
set(busy, MUIA_Busy_Speed, MUIV_Busy_Speed_Off);
return TRUE;
}
MakeStaticHook(hook_StopTrack, hook_StopTrackFunc);
/*
* Construction et ouverture de la fenêtre principale.
* Cette fonction est appelée quand on sait que tout a bien été initialisé.
*/
Object * OpenMainWindow(void)
{
Object *bt_play, *bt_stop, *bt_previous, *bt_next, *bt_pause, *bt_eject;
Object *lv_pistes;
Object *sl_volume;
Object *sl_unit;
Object *str_device;
Object *txt_info;
int i, tracks;
/* Description de l'interface et de ses propriétés */
app = (Object *)ApplicationObject,
MUIA_Application_Author, "corto@guru-meditation.net",
MUIA_Application_Base, "DISKO",
MUIA_Application_Title, "DisKo - Exemple 6",
MUIA_Application_Version, "$VER: DisKo 1.06 (27/07/04)",
MUIA_Application_Copyright, "Mathias PARNAUDEAU",
MUIA_Application_Description, "Player de CD audio minimaliste",
MUIA_Application_HelpFile, NULL,
MUIA_Application_UsedClasses, ClassList,
SubWindow, window = WindowObject,
MUIA_Window_Title, "DisKo - release 6",
MUIA_Window_ID, MAKE_ID('W', 'I', 'N', '1'),
WindowContents, VGroup,
Child, RegisterGroup(Pages),
MUIA_Register_Frame, TRUE,
// Onglet utilisation
Child, VGroup,
Child, HGroup,
Child, lv_pistes = ListviewObject,
MUIA_Listview_List, list = ListObject,
MUIA_Frame, MUIV_Frame_InputList,
//MUIA_List_Format, "P=\33r",
MUIA_List_Active, MUIV_List_Active_Top,
MUIA_List_AutoVisible, TRUE,
End,
End,
Child, VGroup,
Child, Label("Volume"),
Child, sl_volume = SliderObject,
MUIA_Group_Horiz, FALSE,
MUIA_Numeric_Min, 0,
MUIA_Numeric_Max, 100,
MUIA_Numeric_Value, 38,
MUIA_Numeric_Reverse, TRUE,
End,
End,
End,
Child, VSpace(2),
Child, HGroup,
Child, txt_info = TextObject,
MUIA_Frame, MUIV_Frame_Text,
MUIA_Text_Contents, "Information piste",
End,
Child, busy = BusyObject,
MUIA_Busy_Speed, MUIV_Busy_Speed_Off,
End,
End,
Child, HGroup,
Child, bt_previous = KeyButton("Précédent", 'p'),
Child, bt_next = KeyButton("Suivant", 'v'),
Child, bt_play = KeyButton("Jouer", 'j'),
Child, bt_pause = KeyButton("Pause", 'a'),
Child, bt_stop = KeyButton("Arrêter", 's'),
Child, bt_eject = KeyButton("Ejecter", 'e'),
End,
End,
// Onglet configuration
Child, VGroup, GroupFrameT("Lecteur CD"),
Child, ColGroup(2),
Child, Label2("Device"),
Child, str_device = String(config.device, 32),
Child, Label1("Unit" ),
Child, sl_unit = SliderObject,
MUIA_Group_Horiz, TRUE,
MUIA_Numeric_Min, 0,
MUIA_Numeric_Max, 7,
MUIA_Numeric_Value, config.unit,
End,
End,
Child, KeyButton("Valider", 'd'),
End,
End,
End,
End,
End;
if (app){
/* On fixe quelques valeurs et notifications */
DoMethod(window,
MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
app, 2,
MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
DoMethod(busy, MUIM_Busy_Move, FALSE);
tracks = CDM_GetNumTracks();
for (i=1 ; i<=tracks ; i++){
sprintf(titles[i], "Piste %d", i);
DoMethod(list, MUIM_List_InsertSingle, titles[i], MUIV_List_Insert_Bottom);
}
DoMethod(sl_volume, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime,
sl_volume, 2, MUIM_CallHook, &hook_ChangeVolume);
/*
DoMethod(sl_volume, MUIM_Notify, MUIA_Pressed, FALSE,
sl_volume, 2, MUIM_CallHook, &hook_ChangeVolume);
*/
DoMethod(bt_play, MUIM_Notify, MUIA_Pressed, FALSE,
list, 2, MUIM_CallHook, &hook_PlayTrack); //MUIM_List_Jump, MUIV_List_Jump_Active);
DoMethod(bt_stop, MUIM_Notify, MUIA_Pressed, FALSE,
bt_stop, 2, MUIM_CallHook, &hook_StopTrack);
set(list, MUIA_List_Active, MUIV_List_Active_Top);
DoMethod(list, MUIM_Notify, MUIA_List_Active, MUIV_EveryTime,
list, 2, MUIM_CallHook, &hook_PlayTrack);
DoMethod(bt_previous, MUIM_Notify, MUIA_Pressed, FALSE,
list, 3, MUIM_Set, MUIA_List_Active, MUIV_List_Active_Up);
DoMethod(bt_next, MUIM_Notify, MUIA_Pressed, FALSE,
list, 3, MUIM_Set, MUIA_List_Active, MUIV_List_Active_Down);
SetAttrs(window, MUIA_Window_Open, TRUE, TAG_END);
}
return app;
}
/*
* Initialisation et vérification de tout ce qui est nécessaire à la bonne exécution
* de l'application : ouverture des bibliothèques, test de présence des classes MCC, ...
*/
int Initialize(void)
{
int res = 1;
Object *busy = NULL;
int cdm;
// Initialisation de la configuration (chargement éventuel à partir d'un fichier)
#if defined(__amigaos4__)
config.unit = 1;
strcpy(config.device, "a1ide.device");
#elif defined(__MORPHOS__)
config.unit = 2;
strcpy(config.device, "ide.device");
#else
// Pour UAE, ça fonctionne pour moi avec : uaescsi.device, unit 0
config.unit = 1;
strcpy(config.device, "scsi.device");
#endif
IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 39L);
MUIMasterBase = OpenLibrary(MUIMASTER_NAME, MUIMASTER_VMIN);
cdm = CDM_Initialize(config.device, config.unit);
if (IntuitionBase == NULL){
printf("Impossible d'ouvrir 'intuition.library' V39\n");
res = 0;
}
if (MUIMasterBase == NULL){
printf("Impossible d'ouvrir '%s' V%d\n", MUIMASTER_NAME, MUIMASTER_VMIN);
res = 0;
}
#ifdef __amigaos4__
IIntuition = (struct IntuitionIFace *)GetInterface((struct Library *)IntuitionBase, "main", 1, NULL);
if (!IIntuition){
printf("Impossible d'obtenir l'interface IIntuition\n");
res = 0;
}
IMUIMaster = (struct MUIMasterIFace *)GetInterface(MUIMasterBase, "main", 1, NULL);
if (!IMUIMaster){
printf("Impossible d'obtenir l'interface IMUIMaster\n");
res = 0;
}
#endif
if (cdm){
printf("Impossible d'initialiser le module CDDA avec %s, unité %d\n", config.device, config.unit);
printf("Veuillez modifier le périphérique logique et l'unité dans la fonction Initialize() de DisKo6.c\n");
printf("Et vérifiez qu'un CD audio est bien présent dans le lecteur ! ;-)\n");
res = 0;
}
busy = BusyObject, End;
if (busy == NULL){
printf("Classe Busy manquante\n");
res = 0;
}
MUI_DisposeObject(busy);
return res;
}
/*
* Fermeture et libération de tout ce qui a été initialisé au démarrage.
*/
void DeInitialize(void)
{
CloseLibrary(MUIMasterBase);
CloseLibrary((struct Library *)IntuitionBase);
CDM_DeInitialize();
#ifdef __amigaos4__
if (IMUIMaster) {
DropInterface((struct Interface *)IMUIMaster);
}
if (IIntuition) {
DropInterface((struct Interface *)IIntuition);
}
#endif
}
/*
* Programme principal : il appelle les initialisations, ouvre la fenêtre puis gère
* les événements jusqu'à ce qu'on ferme l'application, condition de libération
* des ressources
*/
int main(int argc, char **argv)
{
int res = 0;
if (Initialize()){
app = OpenMainWindow();
if (app){
/* Boucle de gestion des événements, toujours la même */
ULONG sigs = 0;
while (DoMethod(app,MUIM_Application_NewInput,&sigs) != MUIV_Application_ReturnID_Quit)
{
if (sigs)
{
sigs = Wait(sigs | SIGBREAKF_CTRL_C);
if (sigs & SIGBREAKF_CTRL_C) break;
}
}
/* Libération des ressources et fermeture */
set(window, MUIA_Window_Open, FALSE);
MUI_DisposeObject(app);
}else{
res = 2;
}
}else{
res = 1;
}
DeInitialize();
return res;
}
|
Notions acquises dans ce chapitre
- Avantages, conception et écriture d'un module indépendant à la GUI.
- Amélioration de l'interaction entre les composants.
- Révision des hooks, notifications et appels de méthodes
|