1 module soundprocessing.wavfile;
2 
3 import std.conv;
4 
5 import dlib.audio.sound;
6 
7 import soundprocessing.utils;
8 
9 /++
10  + The WavFile class
11  +/
12 public class WavFile
13 {
14     /++
15      + Constructs a new `WavFile` object by arguments
16      +/
17     public static WavFile construct(double duration, uint pitch, string path = "")
18     {
19         throw new Exception("Method not implemented yet!");
20     }
21 
22     private {
23         GenericSound _sound;
24         double _duration;
25         uint _pitch;
26         size_t _dataSize;
27         WavFile[] _files;
28         string _filePath;
29     }
30 
31     public {
32         /++
33          + The `GenericSound` object for the file
34          +/
35         @property GenericSound sound() { return _sound; }
36 
37         /++
38          + The duration of the sound (seconds)
39          +/
40         @property double duration() { return _duration; }
41 
42         /++
43          + The pitch of the sound (sample rate)
44          +/
45         @property uint pitch() { return _pitch; }
46 
47         /++
48          + The total data size for the file (including all sub files)
49          +/
50         @property size_t dataSize() { return _dataSize; }
51 
52         /++
53          + An array of all sub files in the file
54          +/
55         @property WavFile[] files() { return _files; }
56 
57         /++
58          + The path of the wav file. If the `WavFile(GenericSound)` constructor is used, the default value is ""
59          +/
60         @property string filePath() { return _filePath; }
61     }
62 
63     /++
64      + Takes in a path to the wav file to be read from
65      +/
66     this(string path)
67     {
68         this(loadWAVFile(path));
69     }
70 
71     /++
72      + Takes in a sound object and constructs the file using the sound
73      +/
74     this(GenericSound sound)
75     {
76         this._sound = sound;
77         this._duration = _sound.duration;
78         this._pitch = _sound.sampleRate;
79         this._dataSize = sound.data.length.to!size_t;
80         this._files = new WavFile[1];
81         _files[0] = this;
82 
83         this._filePath = "";
84     }
85 
86     /++
87      + Saves the object into a file. `path`'s default value is the objects file path
88      +/
89     public void save(string path = _filePath)
90     {
91         if (path.length == 0) throw new Exception("Output path may not be empty!");
92 
93         GenericSound newSound = new GenericSound(
94             _dataSize,
95             _sound.sampleSize.to!ulong,
96             _duration,
97             _sound.channels,
98             _pitch,
99             _sound.bitDepth,
100             _sound.sampleFormat
101         );
102 
103         int total = 0;
104         foreach (WavFile file; _files)
105         {
106             GenericSound sound = file.sound();
107             foreach (ulong i; 0..sound.size)
108             {
109                 newSound[0, total] = sound[1, i];
110                 total++;
111             }
112         }
113 
114         saveWAVFile(path, newSound);
115     }
116 
117     /++
118      + Combines the file's sound with another file's sound
119      +/
120     public void combine(WavFile target)
121     {
122         // TODO: We should also include all `target`'s sub files?
123 
124         _dataSize += target.dataSize();
125 
126         WavFile[] tempFiles = _files;
127         _files = new WavFile[tempFiles.length + 1];
128 
129         foreach (ulong i; 0..tempFiles.length)
130         {
131             _files[i] = tempFiles[i];
132         }
133 
134         _files[tempFiles.length] = target;
135     }
136 
137     /++
138      + Sets the play duration (seconds)
139      +/
140     public void setDuration(double duration)
141     {
142         _duration = duration;
143     }
144 
145     /++
146      + Sets the pitch (sample rate)
147      +/
148     public void setPitch(uint pitch)
149     {
150         _pitch = pitch;
151     }
152 }
153 
154 unittest
155 {
156     WavFile test1 = new WavFile("Assets/Sound1.wav");
157     test1.setPitch(30_000);
158     test1.setDuration(test1.sound().duration + 1);
159     
160     WavFile test2 = new WavFile("Assets/Sound2.wav");
161     test2.setPitch(78_000);
162     test2.setDuration(test2.sound().duration - 1);
163 
164     test1.combine(test2);
165     test1.save("Assets/UnittestCombined1.wav");
166 
167     test2.combine(test1);
168     test2.save("Assets/UnittestCombined2.wav");
169 }