Dans un nouvel article traduit, nous expliquons comment créer un bip sur différentes plates-formes.
Les E / S audio sont un sujet délicat qui effraie de nombreux musiciens programmeurs et programmeurs amateurs de musique. Essayons de comprendre cela! Dans cet article, nous discuterons du fonctionnement du son sur chaque système d'exploitation moderne (version de bureau).
Notre cas d'aujourd'hui sera considéré en utilisant un simple bip comme exemple. Vous vous souvenez de cette chose ennuyeuse à l'intérieur de votre ordinateur qui fait un bourdonnement désagréable? Maintenant, c'est devenu un simple souvenir! Je suggère de créer une bibliothèque qui reproduit des sons similaires sur tous les OS.
Le résultat final est disponible sur ce lien .
LES FENÊTRES
Nous avons de la chance avec Windows: il existe déjà une fonction Beep (fréquence, durée) dans <utilapiset.h> . Nous pouvons l'utiliser.
Cette fonctionnalité a une histoire très longue et complexe . Il a été introduit pour lire des signaux audio via un bip matériel utilisant la minuterie programmable 8245. Comme de plus en plus d'ordinateurs ont été libérés sans bip, cette fonction est devenue obsolète avec le temps. Cependant, dans Windows 7, il a été réécrit pour lire des signaux audio à l'aide de l'API de la carte son.
Cependant, la simplicité apparente de cette fonctionnalité cache la complexité de toutes les API audio Windows. MME est sorti en 1991 ... Il est utilisé par défaut pour l'audio car il a un bon support.
MME est connu pour avoir une latence de lecture élevée et ne conviendra probablement pas à la plupart des applications audio. Toujours en 2007, WASAPI est sorti . Il a une latence plus faible, en particulier lorsqu'il est utilisé en mode exclusif (un mode dans lequel l'utilisateur ne peut pas écouter Spotify ou toute autre application pendant que votre application est en cours d'exécution). WASAPI est un bon choix pour les applications audio, mais recherchez DirectSound , qui est un wrapper WASAPI pour interagir avec DirectX.
En cas de doute, utilisez WASAPI.
LINUX
L'audio est l'un des rares domaines où l'API Linux est aussi cool que les autres plates-formes. Tout d'abord, il faut dire sur ALSA, qui fait partie du noyau lui-même.
ALSA communique directement avec le matériel, et si vous souhaitez que votre application fonctionne exclusivement avec le son, ALSA peut être un bon compromis entre complexité et performances. Si vous construisez un synthétiseur ou un échantillonneur pour le Raspberry Pi, ALSA est un bon choix.
En outre, il y a PulseAudio, une couche d'abstraction audio construite au-dessus d'ALSA. Il achemine l'audio à partir de diverses applications et essaie de mélanger les flux audio afin que les applications critiques ne souffrent pas de problèmes de latence. Bien que PulseAudio fournisse de nombreuses fonctionnalités qui ne seraient pas possibles avec ALSA (telles que le routage audio sur Internet), la plupart des applications musicales ne l'utilisent pas.
De nombreuses personnes utilisent le kit de connexion audio JACK... JACK a été créé pour les musiciens professionnels. Il prend en charge la lecture en temps réel, tandis que PulseAudio a été créé pour les utilisateurs occasionnels qui peuvent rencontrer un certain décalage lors de la lecture de vidéos YouTube. JACK connecte les applications audio avec une latence minimale, mais gardez à l'esprit qu'il fonctionne toujours sur ALSA, donc si votre application va être la seule application audio en cours d'exécution (par exemple, si vous construisez une boîte à rythmes à partir d'un ancien Raspberry Pi), alors ALSA est beaucoup plus facile à utiliser et de meilleures performances aussi.
Faire une fonction de bip à l'aide d'ALSA n'est en fait pas si difficile. Nous devons ouvrir le périphérique audio par défaut, le configurer pour utiliser une fréquence d'échantillonnage et un format d'échantillonnage bien pris en charge, et commencer à y écrire des données. Les données audio peuvent être une onde en dents de scie, comme décrit dans mon article précédent .
int beep(int freq, int ms) {
static void *pcm = NULL;
if (pcm == NULL) {
if (snd_pcm_open(&pcm, "default", 0, 0)) {
return -1;
}
snd_pcm_set_params(pcm, 1, 3, 1, 8000, 1, 20000);
}
unsigned char buf[2400];
long frames;
long phase;
for (int i = 0; i < ms / 50; i++) {
snd_pcm_prepare(pcm);
for (int j = 0; j < sizeof(buf); j++) {
buf[j] = freq > 0 ? (255 * j * freq / 8000) : 0;
}
int r = snd_pcm_writei(pcm, buf, sizeof(buf));
if (r < 0) {
snd_pcm_recover(pcm, r, 0);
}
}
return 0;
}
Ici, nous utilisons une API synchrone et ne vérifions pas les erreurs pour garder la fonction courte et simple. Les E / S de blocage synchrone ne sont probablement pas la meilleure option pour les applications audio sérieuses, et heureusement, ALSA est livré avec différentes méthodes de transfert et modes de fonctionnement: lien . Mais pour notre simple expérience, cela suffit amplement. En cas de doute, utilisez ALSA. Si vous devez interagir avec d'autres applications audio, utilisez JACK.
MACOS
Dans le cas de MacOS, tout est assez simple, mais pas entièrement élémentaire.
MacOS dispose d'un framework CoreAudio pour les fonctions audio sur le bureau et iOS. CoreAudio lui-même est une API de bas niveau étroitement intégrée au système d'exploitation pour optimiser la latence et les performances. Pour lire de l'audio à l'aide de CoreAudio, vous devez créer un AudioUnit (plug-in audio). L'API AudioUnit est un peu longue, mais facile à comprendre. Voici comment créer un nouvel AudioUnit:
AudioComponent output;
AudioUnit unit;
AudioComponentDescription descr;
AURenderCallbackStruct cb;
AudioStreamBasicDescription stream;
descr.componentType = kAudioUnitType_Output,
descr.componentSubType = kAudioUnitSubType_DefaultOutput,
descr.componentManufacturer = kAudioUnitManufacturer_Apple,
// Actual sound will be generated asynchronously in the callback tone_cb
cb.inputProc = tone_cb;
stream.mFormatID = kAudioFormatLinearPCM;
stream.mFormatFlags = 0;
stream.mSampleRate = 8000;
stream.mBitsPerChannel = 8;
stream.mChannelsPerFrame = 1;
stream.mFramesPerPacket = 1;
stream.mBytesPerFrame = 1;
stream.mBytesPerPacket = 1;
output = AudioComponentFindNext(NULL, &descr);
AudioComponentInstanceNew(output, &unit);
AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &cb, sizeof(cb));
AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0, &stream, sizeof(stream));
AudioUnitInitialize(unit);
AudioOutputUnitStart(unit);
Ce code crée et démarre uniquement un nouvel AudioUnit, la génération de son réelle se produira de manière asynchrone dans le rappel:
static OSStatus tone_cb(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
UInt32 inNumberFrames, AudioBufferList *ioData) {
unsigned int frame;
unsigned char *buf = ioData->mBuffers[0].mData;
unsigned long i = 0;
for (i = 0; i < inNumberFrames; i++) {
buf[i] = beep_freq > 0 ? (255 * theta * beep_freq / 8000) : 0;
theta++;
counter--;
}
return 0;
}
Ce rappel génère le son de la même manière que nous l'avons fait avec ALSA, mais il est appelé de manière asynchrone lorsque CoreAudio pense que le tampon audio est presque vide et doit être rempli de nouveaux échantillons audio.
Cette approche asynchrone de la génération de sons est très courante et presque toutes les bibliothèques audio modernes la prennent en charge. Si vous souhaitez créer une application musicale, vous devez la concevoir en tenant compte de la lecture asynchrone.
En cas de doute, utilisez CoreAudio.
Cela semble compliqué, non?
Si vous créez une application musicale, vous pouvez suivre le même chemin en implémentant un backend audio pour WASAPI, ALSA et CoreAudio. En fait, ce n'est pas si difficile. Vous pouvez voir le code source complet du bip , c'est environ 100 lignes de code pour les trois plates-formes.
Cependant, il existe un certain nombre de bonnes bibliothèques multiplateformes telles que:
- RtAudio + RtMidi (très facile à utiliser, un fichier .cpp et .h);
- PortAudio + PortMidi (écrit en C et légèrement plus grand), a de nombreux backends différents;
- SoundIO est une merveilleuse petite bibliothèque du créateur de Zig.
Certaines personnes préfèrent utiliser JUCE pour les applications audio multiplateformes, mais cela a ses limites.
Tout ce qui précède peut sembler une tâche ardue, mais il existe de nombreuses implémentations, et la plupart sont bonnes. Alors continuez d'essayer!
J'espère que vous avez apprécié cet article. Vous pouvez suivre l'actualité et les projets sur Github , Twitter ou vous abonner via RSS .