% generate sounds. -module(sound). -export([synthesize/3, silence/1]). -include("sound.hrl"). -ifdef(EightBits). silence(Duration) -> silence(Duration, 8). -else. silence(Duration) -> silence(Duration, 16). -endif. %% returns {Fragment, FragmentDuration, Attaque, FadeOut, SamplesPerFragment, LongFragment} %% where Fragment = 1 frame worth of silence (most of the other return values %% are irelevant in this case, there are only there to fit into the ets table). %% Duration in seconds silence(Duration, BitsPerSample) -> %% one framelength worth of silence (0s?) NrOfSamples = trunc(Duration * ?SamplingRate), NrOfBits = NrOfSamples * BitsPerSample, {<<0:NrOfBits>>, Duration, <<>>, <<>>, NrOfSamples, <<>>}. %% returns {Fragment, FragmentDuration, Attack, Fadeout, LongFragment} synthesize(Frequency, Volume, Shape) -> %% the Sound is a number of fragments. the Fragments are synthesized, using a wave formula. %% In order to be able to join them into 1 uniform sound, the fragment should consist of a number %% of complete waves. %% In order to keep the pitch more or less correct, while at the same time %% keeping the number of sinus() calls within limits, the duration of a fragment %% is kept more or less stable, which means the number of waves in the sample varies %% with the pitch. SamplingRate = ?SamplingRate, TargetFragmentDuration = case Frequency of High when High > 200 -> (1/440 * 4); _Low -> (1/440 * 8) end, WavesPerFragment = round(TargetFragmentDuration * Frequency), case Frequency of 0 -> io:format("1- Dividing by 0~n"); _ -> ok end, Dw = 1/Frequency, % duration of 1 wave FragmentDuration = WavesPerFragment * Dw, %% sample WaverPerFragment waves - this is fitted into an exact (integer) number of %% samples. This introduces a small error, but I guess this will be %% acceptable. TargetSamplesPerWave = Dw * SamplingRate, SamplesPerFragment = round(WavesPerFragment*TargetSamplesPerWave), case WavesPerFragment of 0 -> io:format("2- Dividing by 0, frequency: ~p, Volume: ~p~n", [Frequency, Volume]); _ -> ok end, SamplesPerWave = SamplesPerFragment/WavesPerFragment, WaveFunction = case Shape of sinus -> AnglePerSample = 2 * math:pi()/SamplesPerWave, fun(X, Acc) -> sinusWave(X, Volume, AnglePerSample, Acc) end; saw -> fun(X, Acc) -> %% calulate the location in the wave, 0 = extreme left, 100 = extreme right L = remainder(X, SamplesPerWave), LRelative = L/SamplesPerWave * 100, Sample = Volume * 256 * if LRelative < 10 -> LRelative/10; LRelative < 90 -> (10 - LRelative) * 2/80 + 1; true -> - (100 - LRelative)/10 end, <> end end, Fragment = lists:foldl(WaveFunction, <<>>, lists:seq(1, SamplesPerFragment)), %% The 'attaque' gets 2 fragments. The volume (= amplitude) sound increases %% in a linear way. LongFragment = lists:foldl(fun(_X, A) -> <> end, Fragment, lists:seq(1, 15)), Attaque = crescendo(<>, 0, 1), %% The 'fade out' gets 4 fragments. The volume (= amplitude) sound decreases %% in a linear way. FadeOut = crescendo(<>, 1, 0), %% returns {Fragment, FragmentDuration, Attack, Fadeout} {Fragment, FragmentDuration, Attaque, FadeOut, SamplesPerFragment, LongFragment}. remainder(X, Y) -> X - (trunc(X/Y) * Y). crescendo(Sound, From, To) -> NrOfSamples = size(Sound) div 2, crescendo(Sound, From, (To - From)/NrOfSamples, <<>>). crescendo(<<>>, _From, _Step, Acc) -> Acc; crescendo(<>, Current, Step, Acc) -> S2 = round(S * Current), crescendo(Tail, Current + Step, Step, <>). -ifdef(EightBits). sinusWave(X, Amp, AnglePerSample, Acc) -> <>. -endif. -ifdef(SixteenBits). sinusWave(X, Amp, AnglePerSample, Acc) -> <>. %% <>. -endif.