1 module evael.audio.Sound; 2 3 import derelict.sndfile.sndfile; 4 5 import evael.audio.AL; 6 import evael.audio.Source; 7 import evael.audio.AudioException; 8 9 import evael.system.Asset; 10 11 import evael.utils.Config; 12 13 import dnogc.DynamicArray; 14 15 /** 16 * Sound asset. 17 */ 18 class Sound : IAsset 19 { 20 private uint m_buffer; 21 22 /// List of sources for this sound. 23 private DynamicArray!uint m_sources; 24 25 /** 26 * Sound constructor. 27 * Params: 28 * buffer: AL buffer id 29 */ 30 @nogc 31 public this(in uint buffer) nothrow 32 { 33 this.m_buffer = buffer; 34 } 35 36 /** 37 * Sound destructor. 38 */ 39 @nogc 40 public void dispose() nothrow 41 { 42 foreach (id; this.m_sources) 43 { 44 al.DeleteSources(1, &id); 45 } 46 47 al.DeleteBuffers(1, &this.m_buffer); 48 } 49 50 /** 51 * Creates a new source linked to this sound resource. 52 */ 53 @nogc 54 public Source createSource() nothrow 55 { 56 uint id; 57 al.GenSources(1, &id); 58 59 this.m_sources.insert(id); 60 61 return Source(id, this.m_buffer); 62 } 63 64 /** 65 * Loads sound. 66 * Params: 67 * soundName: sound to load 68 */ 69 public static Sound load(in string soundName) 70 { 71 import std.string : format, toStringz; 72 73 auto fileName = toStringz(Config.Paths.sounds!string ~ soundName); 74 75 SF_INFO fileInfo; 76 77 auto file = sf_open(fileName, SFM_READ, &fileInfo); 78 79 if (!file) 80 { 81 throw new AudioFileNotFoundException(format("Can't open file %s", soundName)); 82 } 83 84 short[] samples = new short[cast(uint)fileInfo.frames]; 85 86 if (sf_read_short(file, samples.ptr, fileInfo.frames) != fileInfo.frames) 87 { 88 throw new Exception("Something is wrong"); 89 } 90 91 auto fileFormat = getFormatFromChannelCount(fileInfo.channels); 92 93 if (fileFormat == 0) 94 { 95 throw new AudioUnsupportedChannelsException(format("Can't open file %s, unsupported number of channels %d", soundName, fileInfo.channels)); 96 } 97 98 uint buffer; 99 100 // Create the buffer 101 al.GenBuffers(1, &buffer); 102 al.BufferData(buffer, fileFormat, samples.ptr, ushort.sizeof * samples.length, fileInfo.samplerate); 103 104 return new Sound(buffer); 105 } 106 107 /** 108 * Finds the sound format according to the number of channels. 109 * Params: 110 * channelCount: number of channels 111 */ 112 @nogc 113 public static int getFormatFromChannelCount(in uint channelCount) nothrow 114 { 115 int format; 116 117 switch (channelCount) 118 { 119 case 1: format = AL_FORMAT_MONO16; break; 120 case 2: format = AL_FORMAT_STEREO16; break; 121 case 4: format = alGetEnumValue("AL_FORMAT_QUAD16"); break; 122 case 6: format = alGetEnumValue("AL_FORMAT_51CHN16"); break; 123 case 7: format = alGetEnumValue("AL_FORMAT_61CHN16"); break; 124 case 8: format = alGetEnumValue("AL_FORMAT_71CHN16"); break; 125 default: format = 0; break; 126 } 127 128 return format; 129 } 130 131 }