声音和音乐
SFML 提供两个用于播放音频的类:sf::Sound 和 sf::Music。它们都提供了大致相同的功能,主要区别在于它们的工作方式。
sf::Sound 是一个轻量级对象,它可以播放从 sf::SoundBuffer 中加载的音频数据。它应该用于可以适应内存并且在播放时不会出现延迟的小音效。例如枪声、脚步声等。
sf::Music 不会将所有音频数据加载到内存中,而是从源文件实时流式传输它们。它通常用于播放持续几分钟的压缩音乐,否则会需要多秒钟才能加载并占用数百 MB 的内存。
加载并播放声音
如上所述,声音数据并不直接存储在 sf::Sound 中,而是存储在一个名为 sf::SoundBuffer 的单独类中。该类封装了音频数据,它基本上是一个由 16 位有符号整数(称为 “音频样本”)组成的数组。样本是在给定时间点上声音信号的振幅,因此样本数组表示了完整的声音。
事实上,sf::Sound / sf::SoundBuffer 类与来自图形模块的 sf::Sprite / sf::Texture 类使用相同的方式。因此,如果您了解精灵和纹理如何配合使用,那么可以将相同的概念应用于声音和声音缓冲区。
您可以使用 loadFromFile 函数从磁盘上的文件加载声音缓冲区:
#include <SFML/Audio.hpp>
int main()
{
sf::SoundBuffer buffer;
if (!buffer.loadFromFile("sound.wav"))
return -1;
...
return 0;
}
与其他内容一样,您还可以从内存(loadFromMemory)或自定义输入流(loadFromStream)中加载音频文件。
SFML支持音频文件格式WAV,OGG / Vorbis和FLAC。由于许可问题,不支持MP3。
如果来自另一个源的情况下,您还可以直接从样本数组加载声音缓冲区:
std::vector<sf::Int16> samples = ...;
buffer.loadFromSamples(&samples[0], samples.size(), 2, 44100);
由于loadFromSamples加载的是样本的原始数组,而不是音频文件,因此它需要其他参数才能完全描述声音。第一个参数(第三个参数)是通道数;1个通道定义为单声道声音,2个通道定义为立体声声音,等等。第二个附加属性(第四个参数)是采样率;它定义了每秒必须播放多少样本才能重建原始声音。
现在,音频数据已加载,我们可以使用sf::Sound实例播放它。
sf::SoundBuffer buffer;
// load something into the sound buffer...
sf::Sound sound;
sound.setBuffer(buffer);
sound.play();
有趣的是,如果需要,您可以将相同的音频缓冲区分配给多个声音。甚至可以同时播放它们而不会出现任何问题。
声音(和音乐)在单独的线程中播放。这意味着在调用play()后,您可以自由地执行任何操作(当然不能销毁声音或其数据),直到声音播放完成或明确停止为止。
播放音乐
与sf :: Sound不同,sf :: Music不会预加载音频数据,而是直接从源流式传输数据。因此,音乐的初始化更加直接:
sf::Music music;
if (!music.openFromFile("music.ogg"))
return -1; // error
music.play();
需要注意的是,与所有其他SFML资源不同,加载函数的名称命名为openFromFile而不是loadFromFile。这是因为音乐并没有真正地加载,这个方法仅仅是打开了音乐文件。数据只有在播放音乐时才会被加载。此外,需要记住,只要音乐在播放,音频文件就必须保持可用。其他的加载函数sf::Music也遵循同样的约定:openFromMemory,openFromStream。
接下来做什么?
现在,你已经可以加载和播放声音或音乐了,让我们看看你可以做什么。
要控制播放,可以使用以下函数:
- play 开始或恢复播放
- pause 暂停播放
- stop 停止播放并倒回
- setPlayingOffset 更改当前播放位置
例子:
// start playback
sound.play();
// advance to 2 seconds
sound.setPlayingOffset(sf::seconds(2.f));
// pause playback
sound.pause();
// resume playback
sound.play();
// stop playback and rewind
sound.stop();
getStatus函数返回声音或音乐的当前状态,可以使用它来知道它是停止、播放还是暂停。
声音和音乐的播放也受到几个属性的控制,这些属性可以在任何时候进行更改。
音高是改变声音的感知频率的因素:大于1会以更高的音高播放声音,小于1会以更低的音高播放声音,而1则保持不变。更改音高会产生一个副作用:它会影响播放速度。
sound.setPitch(1.2f);
音量就是…音量。其值从0(静音)到100(最大音量)范围内。默认值是100,这意味着您不能将声音调得比其初始音量更大。
sound.setVolume(50.f);
循环属性控制声音/音乐是否自动循环播放。如果设置了循环,它将在完成后从头开始重新播放,直到您明确调用停止为止重复播放。如果未设置循环,则在完成后会自动停止播放。
sound.setLoop(true);
还有其他属性可供使用,但它们与空间化有关,将在相应的教程中进行解释。
常见错误
已销毁的声音缓冲区
最常见的错误是当声音仍在使用缓冲区时,使缓冲区超出范围(因此被销毁)。
sf::Sound loadSound(std::string filename)
{
sf::SoundBuffer buffer; // this buffer is local to the function, it will be destroyed...
buffer.loadFromFile(filename);
return sf::Sound(buffer);
} // ... here
sf::Sound sound = loadSound("s.wav");
sound.play(); // ERROR: the sound's buffer no longer exists, the behavior is undefined
请记住,声音只保持对您提供给它的声音缓冲区的指针,它不包含自己的副本。您必须正确管理声音缓冲区的生命周期,以使它们在被声音使用时保持活动状态。
太多的声音
另一个错误的源头是尝试创建大量的声音。SFML内部有一个限制;它可以因操作系统而异,但您绝不能超过256个。这个限制是同时存在的sf::Sound和sf::Music实例的数量。保持低于限制的好方法是在不再需要时销毁(或回收)未使用的声音。当然,这只适用于您必须管理大量声音和音乐的情况。
在音乐正在播放时销毁音源
请记住,只要音乐正在播放,它就需要保持音源有效。当您的应用程序播放来自内存中的文件或自定义输入流的音乐时,情况会变得更加复杂:
// we start with a music file in memory (imagine that we extracted it from a zip archive)
std::vector<char> fileData = ...;
// we play it
sf::Music music;
music.openFromMemory(&fileData[0], fileData.size());
music.play();
// "ok, it seems that we don't need the source file any longer"
fileData.clear();
// ERROR: the music was still streaming the contents of fileData! The behavior is now undefined
sf::Music是不能复制的
最后一个需要注意的提示是:sf::Music类是不可复制的,因此您不能复制音乐对象。因此,您不能使用复制构造函数或赋值运算符来创建一个新的音乐对象。如果您需要将音乐对象传递给函数,应该使用引用或指针。
sf::Music music;
sf::Music anotherMusic = music; // ERROR
void doSomething(sf::Music music)
{
...
}
sf::Music music;
doSomething(music); // ERROR (the function should take its argument by reference, not by value)