Téléchargement:
Mixer.zip
(61Ko) |
Un
mixer audio complet sous-classé |
 |
J'ai réalisé ce
mixer pour mon usage personnel, car je voulais voir en un coup d'œil toutes les
lignes et tous les contrôles respectifs, ce qui n'est pas le cas avec les
mixers de Windows.
De plus, le mixer de Windows ne gère pas certains contrôles (par exemple les
MIXERCONTROL_CONTROLTYPE_SINGLESELECT).
Ce projet est assez compliqué, d'autant qu'une partie importante du code sert
à créer et à positionner les différents contrôles audio de façon dynamique
selon les caractéristiques du mixer.
Pour gagner de la place, les noms des
contrôles ("Volume", "Mute", "Select", "Boost",
...) apparaissent en Tool Tip Text.
Je vais juste ici présenter les grandes lignes et le principe général.
Le
sous-classement :
Lorsque d'autres applis ou d'autres mixers modifient les réglages de
certains contôles (volume, mute, ...), il faut actualiser la position
des curseurs et les valeurs des contrôles sur notre mixer. C'est ce qu'on
appelle le rafraîchissement.
|
2 méthodes sont
possibles :
- Utiliser un
Timer, et rafraîchir systématiquement tous les contrôles à intervalles
réguliers. Méthode brutale, consommatrice de CPU et peu élégante.
- Récupérer
les messages en provenance du mixer qui informent qu'un contrôle précis a
été modifié. On ne rafraîchit alors que ce contrôle-là.
C'est l'objet du sous-classement qui permet d'intercepter ces messages afin
de les traiter.
Exception
:
le seul contrôle ne pouvant pas être rafraîchi par sous-classement est le
vu-mètre, car il change tout le temps de valeur, et le mixer n'envoie aucun
message à son sujet. On est donc obligé d'utiliser un Timer spécialement pour
le vu-mètre (s'il y en a un pour le mixer en cours).
Précautions
sur le sous-classement en général :
Le code VB sous-classé est très difficile à debugger, car en mode
interprétation (non compilé), le programme s'exécute au sein de VB lui-même et
intercepte donc tous les messages (procédure "WndProc").
En cas de bug, impossible de stopper l'exécution, il faut tuer le process de VB
complètement, et relancer le tout.
C'est pourquoi je conseille de ne pas sous-classer lors de la mise au point du
code. Le sous-classement n'est pas indispensable pour tester les différentes
procédures, les contrôles ne sont plus rafraîchis automatiquement, c'est
tout.
Pour supprimer temporairement le sous-classement, il suffit de mettre en
commentaire la ligne suivante :
' On sous-classe
' SubclassForm Me
qui se trouve à la fin de Private Sub Form_Load()
- On cherche la
liste des mixers disponibles, et on les affiche dans des OptionButton.
- Dans le cas
où il y a plusieurs mixers (= plusieurs cartes son sur le même ordinateur
- et oui, c'est possible), on recherche le mixer défini par défaut.
- On recherche
le nombre de lignes (en général, 2 ou 3) grâce à l'API mixerGetDevCaps.
Pour chaque ligne, il existe un contrôle-ligne de destination, et
plusieurs contrôles-ligne source. On trouve les caractéristiques
d'une ligne avec l'API :
mixerGetLineInfo (hmixer,
mxl, MIXER_GETLINEINFOF_DESTINATION) où
mxl est un MIXERLINE.
ou bien mixerGetLineInfo (hmixer,
mxl, MIXER_GETLINEINFOF_SOURCE) selon le
cas.
- Pour chaque
contrôle-ligne trouvé grâce au membre dwLineID
commun à mxl et mxlc
(objet MIXERLINECONTROL),
il peut exister 0, 1 ou plusieurs réglages possibles. Chacun de ces
réglages est un objet MIXERCONTROL.
On récupère tous les MIXERCONTROL grâce à l'API :
mixerGetLineControls (hmixer, mxlc,
MIXER_GETLINECONTROLSF_ALL)
et mxlc renvoie un pointeur vers un
tableau composé de plusieurs mxc, un par
contrôle.
Exemple
: Ligne
"Vers Haut-Parleurs" est une ligne (mxl
= MIXERLINE)
"Wave"
est un contrôle-ligne (mxlc = MIXERLINECONTROL)
Le
réglage "Volume" ou le "Mute" sont des
contrôles (mxc = MIXERCONTROL)
- Ces contrôles
peuvent être de types variés. Pour le moment, le projet ne traite que les
contrôles de types suivants :
type |
Explication |
Exemple |
MIXERCONTROL_CONTROLTYPE_VOLUME |
Volume |
Réglage des
volumes (slider) |
MIXERCONTROL_CONTROLTYPE_MUTE |
Mute |
Muet (case
à cocher) |
MIXERCONTROL_CONTROLTYPE_ONOFF |
On-Off |
Gain
supplémentaire (Boost) à l'entrée micro (Case à cocher) |
MIXERCONTROL_CONTROLTYPE_PEAKMETER |
PeakMeter |
Vu-mètre de
niveau |
MIXERCONTROL_CONTROLTYPE_SINGLESELECT |
Sélection
unique parmi plusieurs choix |
Choix d'un
effet (Large, 3D, aucun). Choix non cumulatif (ComboBox) |
MIXERCONTROL_CONTROLTYPE_MIXER |
Multiplexeur
à plusieurs entrées |
Souvent
utilisé pour le contrôle-ligne de destination
"Enregistrement"
Plusieurs entrées possibles. |
MIXERCONTROL_CONTROLTYPE_MUX |
Multiplexeur
à une seule entrée |
Souvent
utilisé pour le contrôle-ligne de destination
"Enregistrement"
Une seule entrée possible |
Il existe
d'autres types de contrôle plus rarement rencontrés. Le projet effectue une
recherche exhaustive de TOUS les contrôles existants pour le mixer en
cours, mais ne traite que ceux des types mentionnés. Cependant, on peut voir la
liste des contrôles non traités et leur description en cliquant sur le bouton
"Info" (invisible s'il n'y a aucun contrôle non traité).
Les
procédures principales du Form |
Notre objectif : relever tous les
MIXERCONTROL (= mxc) existants,
et remplir un tableau mxc() qu'on redimensionnera au fur et à mesure
(Redim Preserve)
Les membres de
mxc() qui nous intéressent particulièrement sont : |
- Le nom
court |
szShortName |
- Le nom
long |
szName |
- Son identificateur (numéro
unique) |
dwControlID |
- Sa nature
(volume, mute, réglage ON/OFF, ...) |
dwControlType |
- Ses valeurs possibles MAX et
MIN |
lMaximum et lMinimum |
- Dans le cas particulier d'un contrôle de type
Multipexeur |
cMultipleItems |
REMARQUE À PROPOS DE LA STRUCTURE MIXERCONTROL
=============================================
J'ai dû changer la nature des 2 membres suivants qui sont dans les docs :
szShortName As String * MIXER_SHORT_NAME_CHARS
szName As String * MIXER_LONG_NAME_CHARS
Et que j'ai modifiés de la manière suivante :
szShortName(MIXER_SHORT_NAME_CHARS - 1) As Byte
szName(MIXER_LONG_NAME_CHARS - 1) As Byte
Car les tableaux mxc() ne fonctionnaient pas, probablement dû à des questions de texte ASCII ou UNICODE (caractères codés sur 1 ou 2 octets)
Pour récupérer un nom sous forme de texte, il faut maintenant faire :
ByteToString(mxc(I).szName, MIXER_LONG_NAME_CHARS - 1)
- LoadControls
recherche tous les contrôles, et charge un tableau MIXERCONTROL (variable
mxc).
Pour chaque contrôle trouvé, il appelle AddNewMixerControl, et
éventuellement RefreshControlMixer pour rafraîchir le nouveau contrôle.
- AddNewLineControl
ajoute une nouvelle ligne (2 ou 3 lignes au total en général).
- AddNewMixerControl
ajoute un nouveau contrôle. On utilise la propriété "ToolTiptext"
pour indiquer le nom du contrôle chargé, et la propriété "Tag"
pour archiver le nombre de canaux ou l'identificateur du contrôle selon le
cas.
- RefreshControlMixer
: rafraîchit un seul contrôle précis.
- SetControlMixer
: Cette procédure attribue une ou plusieurs valeurs au contrôle spécifié
après l'intervention de l'utilisateur (seul le vu-mètre n'apparaît pas
dans cette procédure, car c'est un contrôle en lecture seule).
Les
procédures principales du module |
- GetVolumeLevel
et SetVolumeLevel : récupère ou attribue une
(ou plusieurs si stéréo) valeur(s) de type MIXERCONTROLDETAILS_UNSIGNED à
un contrôle. C'est le cas des réglages de volume.
- GetControlBoolean
et SetControlBoolean : récupère ou attribue
une valeur de type MIXERCONTROLDETAILS_BOOLEAN (VRAI ou FAUX) à un
contrôle. C'est le cas du Mute (Muet ou pas).
- GetMuxControlList
: récupère la liste des contrôles d'entrée possibles pour un
multiplexeur.
- GetMuxControlValue
et SetMuxControlValue : récupère ou
sélectionne le ou les contrôles actifs parmi la liste des entrées
possibles.
Principes
de la gestion des contrôles graphiques |
- Les
contrôles-lignes (MIXERLINECONTROL) : au fur et à mesure qu'on trouve un nouveau
contrôle-ligne (source ou destination), on charge dynamiquement un
rectangle noir (Shape_mxl) et son label
associé (Lbl_mxl) grâce l'instruction Load.
- Les
contrôles-mixer (MIXERCONTROL) : pour chaque contrôle-ligne, on
recherche tous les contrôles-mixer existants. Pour chacun d'entre eux, on
incrémente l'indice du tableau mxc() :
Redim Preserve mxc(UBound(mxc) + 1)
Chacun d'entre eux est référencé par un numéro unique, son
identificateur qui est le membre dwControlID
de
l'objet
mxc(..).
Ainsi, le tableau mxc() contiendra à la fin
tous les contrôles-mixer.
De plus, pour chaque contrôle-mixer, on doit créer un (ou plusieurs)
contrôle graphique associé.
Par exemple, pour un contrôle de type Volume, on crée un slider vertical (VSControl),
et s'il y a plusieurs canaux (stéréo), on crée aussi un slider horizontal
(HSControl) pour la balance.
La difficulté est qu'on doit absolument :
- 1) retrouver
l'objet MIXERCONTROL qui correspond au contrôle graphique manipulé. Par
exemple, lorsqu'on veut augmenter le volume avec un slider, on doit
connaître l'identificateur du MIXERCONTROL. Pour cela, on fait en sorte que
l'index du contrôle graphique soit égal à l'identificateur.
Exemple : suite à la manipulation d'un slider vertical, l'événement
suivant est provoqué :
Private Sub VSControl_Scroll(Index As
Integer)
SetControlMixer CLng(Index)
End Sub
L'index du contrôle graphique est utilisé comme argument dans la
fonction SetControlMixer, puisque cet index est aussi l'identificateur du
MIXERCONTROL.
- 2) retrouver
l'objet MIXERCONTROL d'après son identificateur. C'est indispensable pour
le rafraîchissement, car lorsque le mixer nous envoie un message, il nous
informe seulement de l'identificateur du contrôle qui a changé. Pour
retrouver l'objet MIXERCONTROL entier, il faut connaître l'indice
correspondant dans le tableau mxc(). C'est le
rôle du tableau Integer : IndArrayMXC.
Exemple : dans la procédure RefreshControlMixer(IndexControlID As Long),
la 1ère chose qu'on fait est de retrouver le MIXERCONTROL :
' On récupère le MIXERCONTROL
mxcLOC = mxc(IndArrayMXC(IndexControlID))
- Tout ceci
serait encore assez simple s'il n'y avait pas les multiplexeurs.
Un contrôle multiplexeur permet de sélectionner un élément (MIXERCONTROL_CONTROLTYPE_MUX)
ou
plusieurs éléments (MIXERCONTROL_CONTROLTYPE_MIXER)
dans une
liste.
Les mixers ont tous une ligne "Vers
Enregistreur", et le contrôle-ligne associé "Contrôle
d'enregistrement".
Ce contrôle-destination accepte plusieurs contrôles-source comme entrées,
et l'utilisateur peut sélectionner une ou plusieurs entrées. C'est le
principe d'un multiplexeur.
Selon les mixers, ceci est géré soit par des contrôles de type
"Mute" qui permettent de rendre muettes les entrées qu'on ne veut
pas, soit de type "Multiplexeur" qui est plus compliqué.
Dans ce cas, c'est le contrôle-ligne de destination qui contient un
contrôle-multiplexeur unique.
Normalement, on devrait alors charger un ComboBox ou une liste à choix
multiples dans le cadre du contrôle-destination, y indiquer tous les
éléments de la liste, et permettre à l'utilisateur de faire ses
sélections. Ce serait la méthode la plus simple, et la plus cohérente.
Mais par manque de place (il faudrait alors agrandir démesurément le
cadre), j'ai préféré créer une case à cocher par élément de la liste
(ChkSelect), et les placer, non pas dans le cadre du contrôle-destination
où ils devraient logiquement être,
mais dans les cadres des contrôles-source (le mixer de Windows adopte aussi
cette présentation).
Et pour les placer correctement, on procède à une identification d'après
les noms : chaque élément de la liste est en fait aussi le nom d'une
entrée possible.
Ainsi, dans la procédure AddNewMixerControl,
on a le code suivant :
' On récupère la liste des entrées possibles dans TextListMU
(tableau de type texte)
' mxcLOC.cMultipleItems est le nombre d'éléments dans cette liste
GetMuxControlList hmixer, Ind, mxcLOC.cMultipleItems, TextListMU
For J = 0 To mxcLOC.cMultipleItems - 1
K = -1
Do While (K < NbLineControlSource)
K = K + 1
' Si TextListMU(J) est le nom du contrôle-ligne source
If TextListMU(J) = Lbl_mxl(Lbl_mxl.UBound - K).Caption Then
' On crée une nouvelle case à cocher
Load ChkSelect(ChkSelect.UBound + 1)
With ChkSelect(ChkSelect.UBound)
......... ' (on positionne la case à cocher)
.........
' Dans ce cas, on utilisera le TAG pour retrouver l'ID du contrôle
.Tag = Ind
.ToolTipText = "Select"
.Visible = True
End With
Exit Do
End If
Loop
Next J
Note : on en peut pas, contrairement aux autres contrôles,
avoir l'index égal à l'identificateur du MIXERCONTROL, puisque toutes ces
cases à cocher se réfèrent à un unique MIXERCONTROL de type
multiplexeur.
Donc, pour retrouver l'identificateur plus tard, lorsqu'on cochera ou
décochera une case, on utilise la propriété .Tag à qui on attribue la
valeur de l'identificateur.