- Home /
Is it possible to create sound with scripting?
I would like to know if it is possible create sound (a small "bleep" is good enough for now.) using only scripts (like how video games from the '70s/'80s/'90s did.), if so, how would it be scripted (C-sharp only!)?;
Yes, I am aware you can import sound files (.wav, .mp3, .ogg, .midi, etc.) from your computer into Unity, but I want to experiment with this and try to create a little song using only scripting.
If you know (or have some ideas on) how this could be made (possible), please let me know. Thank you.
Answer by Bunny83 · Oct 08, 2017 at 01:18 AM
Yes you can. You just need to create PCM samples yourself. So you would create an AudioClip as well as a float array of the desired length. Keep in mind that the usual sample frequency is 44 kHz (44000 samples per second).
Note that stereo sounds have two channels which are alternating. So the first value in the array belongs to the first channel, the second value to the second channel. The third value to the first channel and so on.
Once you have your array setup you just call SetData and pass the float array to the AudioClip.
You can use the trigonometric functions from Mathf to create a sine wave.
int sampleFreq = 44000;
float frequency = 440;
float[] samples = new float[44000];
for(int i = 0; i < samples.Length; i++)
{
samples[i] = Mathf.Sin(Mathf.PI*2*i*frequency/sampleFreq);
}
AudioClip ac = AudioClip.Create("Test", samples.Length, 1, sampleFreq, false);
ac.SetData(samples, 0);
This creates a mono (single channel) audio clip of 1 second length with a beep sound of 440 Hz.
Note that the hardest part would be to get smooth transitions between different frequencies. Sudden changes in frequency or phase would result in an annoying "click" sound. It's important to have a continuous signal (so no sudden jumps in the amplitude or jumps in the derivative of the signal). Even than you usually would need a transition section between two notes if you want to play them right next to each other.
Of course you're not limited to sine waves. You can also create rectangle, triangle or sawtooth waves.
// rectangle
for(int i = 0; i < samples.Length; i++)
samples[i] = ($$anonymous$$athf.Repeat(i*frequency/sampleFreq,1) > 0.5f)?1f:-1f;
// sawtooth
for(int i = 0; i < samples.Length; i++)
samples[i] = $$anonymous$$athf.Repeat(i*frequency/sampleFreq,1)*2f - 1f;
// triangle
for(int i = 0; i < samples.Length; i++)
samples[i] = $$anonymous$$athf.PingPong(i*2f*frequency/sampleFreq,1)*2f - 1f;
(ps: written from scratch and not tested.)
Answer by PatrickPerron · May 22, 2019 at 03:52 PM
Hi, if you just want to add a Beep sound with a frequency and duration on a Windows system, I'm using this code (will try to find a way to do the same in Ubuntu):
Add your using entry:
using System.Runtime.InteropServices;
Then in your main class, add this:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool Beep(uint dwFreq, uint dwDuration);
static void PlayBeep(uint iFrequency, uint iDuration)
{
Beep(iFrequency, iDuration);
}
You can then just call this in your code:
PlayBeep(4000, 100);
This would play a beep sound at frequency 4000 for 100ms... I tested this code with a Windows 10 x64 machine and Unity 2018.3.11f.
This will only work on Windows systems as you are importing the OS specific kernel32.dll. Bunny83's answer, although more complicated, is a generic solution.
Answer by asashour · Jun 24, 2020 at 01:15 PM
The example in AudioClip.Create works.
You need to have ExampleClass attached to a GameObject, which also has an empty Audio Source component.
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour
{
public int position = 0;
public int samplerate = 44100;
public float frequency = 440;
void Start()
{
AudioClip myClip = AudioClip.Create("MySinusoid", samplerate * 2, 1, samplerate, true, OnAudioRead, OnAudioSetPosition);
AudioSource aud = GetComponent<AudioSource>();
aud.clip = myClip;
aud.Play();
}
void OnAudioRead(float[] data)
{
int count = 0;
while (count < data.Length)
{
data[count] = Mathf.Sin(2 * Mathf.PI * frequency * position / samplerate);
position++;
count++;
}
}
void OnAudioSetPosition(int newPosition)
{
position = newPosition;
}
}
Answer by PaulRdy · Sep 05, 2018 at 12:21 PM
As of September 2018 (Unity 2018.2) this isn't working. Is there another possiblity to do this? AudioClip.Create has been marked as obsolete but I can't find a replacement.
To expand on myzzie's comment, you can see that there are newer versions of the mentioned functions, with somewhat different signatures. As far as I can see, the only differences is that they removed the bool _3D parameter.
Also, next time please create a new question and link to this one. One shouldn't post a new question as an answer.
I tried the top most overload (pretty much using the exact example provided on the page, but without custom callbacks but SetData ins$$anonymous$$d). Didn't work.
int sampleRate = 44100;
float[] samples = new float[44100];
clip = AudioClip.Create("test", sampleRate, 1, sampleRate, false);
for (int i = 0; i < samples.Length; i++)
{
samples[i] = $$anonymous$$athf.Sin($$anonymous$$athf.PI * 2 * 440 * i * / sampleRate);
}
clip.SetData(samples, 0);
source.PlayOneShot(clip, 1.0f);
Edit: Also the example provided by Bunny. So you're saying I need to use the custom callbacks?
I don't think the custom callbacks are needed, they are just to update the sound data and its position in 3D.
Your code seems to be correct in general, but I have a feeling that the problem is that samples is a local variable that goes out of scope, and I'm not sure AudioClip.SetData() makes a copy of it. $$anonymous$$aking the samples into a member variable would solve that.
BTW, what do you mean when you say "it doesn't work"? Does it make any noise, or none at all? Do you get any errors in the console?
$$anonymous$$akes no noise and I don't get any errors. But the method is definitely getting called.
I also tried making the samples array a class member as you suggested. Unfortunately that didn't fix anything either.
Also the VS auto complete says that the AudioClip.Create overload I'm using is obsolete, contrary to what's said in the documentation. This is not a feature I desperately need so it's not terrible but yeah... I'm on Unity 2018.2.1f1 btw.
Edit:
just did:
if (i % 40 == 0)
Debug.Log(samples[i]);
samples array seems to be correct. So it just seems that array get's lost somehow. But it's in a $$anonymous$$onoBehaviour class and the function is being called via a trigger in the Update method to exclude any threading issues. Still doesn't work.
Your answer