jfversluis / Plugin.Maui.Audio

Plugin.Maui.Audio provides the ability to play audio inside a .NET MAUI application
MIT License
261 stars 46 forks source link

Generate tones without having to use sound files #50

Open leoderja opened 1 year ago

leoderja commented 1 year ago

It would be great if there was a way to simply generate a tone with .Net Maui, just indicating frequency, volume, duration, (waveform?), etc, without using an audio file.

Do you think it is possible to add this functionality to Plugin.Maui.Audio?

Thank you a lot!

jfversluis commented 1 year ago

Cool idea! Not something I’d think of immediately but why not 😄 not sure we will add it anytime soon, but open to PRs for this!

bijington commented 1 year ago

I would love something like this in an upcoming project but I don't have a clue where to start

Xo3-ToBapbl commented 1 year ago

In our project, we also needed mock audio but for recorder control, and we used the following approach: SineGenerator helper to generate audio tone at runtime:

public class SineGenerator
{
    public static short[] GenerateSine(double frequency, uint sampleRate, int milliSeconds)
    {
        var secondsInLength = milliSeconds / 1000d;
        var bufferSize = (ushort)(sampleRate * secondsInLength);
        var dataBuffer = new short[bufferSize];

        const int amplitude = 32760;
        double timePeriod = Math.PI * 2 * frequency / sampleRate;

        for (uint index = 0; index < bufferSize - 1; index++)
        {
            dataBuffer[index] = Convert.ToInt16(amplitude * Math.Sin(timePeriod * index));
        }

        return dataBuffer;
    }
}

AudioDataChunk to store generated tone and write it to the buffer later:

public class AudioDataChunk
{
    public uint ChunkSize { get; set; }
    public short[] WaveData { get; private set; }
    public byte[] WaveDataBytes { get; private set; }

    public AudioDataChunk()
    {
        ChunkSize = 0;
        WaveData = Array.Empty<short>();
    }

    public void AddSampleData(short[] leftChannelBuffer, short[] rightChannelBuffer)
    {
        WaveData = new short[leftChannelBuffer.Length + rightChannelBuffer.Length];
        int bufferOffset = 0;
        for (int index = 0; index < WaveData.Length; index += 2)
        {
            WaveData[index] = leftChannelBuffer[bufferOffset];
            WaveData[index + 1] = rightChannelBuffer[bufferOffset];
            bufferOffset++;
        }
        ChunkSize = (uint)WaveData.Length * 2;
        WaveDataBytes = WaveData.SelectMany(BitConverter.GetBytes).ToArray();
    }
}

How to use it?

  1. Generate and save tone:
    _sineToneChunk = new AudioDataChunk();
    _sineToneChunk.AddSampleData(
    leftChannelBuffer: SineGenerator.GenerateSine(697.0f, (uint)AudioStreamDetails.SampleRate, ToneLengthMs),
    rightChannelBuffer: SineGenerator.GenerateSine(1209.0f, (uint)AudioStreamDetails.SampleRate, ToneLengthMs));
  2. Write wherever you need:
    _binaryWriter = new BinaryWriter(_streamWriter.BaseStream, System.Text.Encoding.UTF8);
    _binaryWriter.Write(_sineToneChunk.WaveDataBytes);

    This is solution is based on: https://www.codeguru.com/dotnet/making-sounds-with-waves-using-c/ I hope this would be a good starting point for you.

davepruitt commented 1 year ago

I found the comment by @Xo3-ToBapbl to be very helpful, as well as the codeguru link that he provided. However, they were still not 100% effective for me.

The code at the codeguru website worked well, even without changes. But I wanted a couple of extra pieces of functionality:

  1. The ability add silence into a sound
  2. The ability to add multiple pieces of sound together into one sound.

I was able to do this by making some small changes to the DataChunk.cs class at the codeguru website. I also created a new class called SilenceGenerator.

For those interested in my solution, the complete set of classes can be found here:

https://github.com/ReWire-LLC/rewire_pulse_oximeter_software_maui/tree/main/PulseOximeter/PulseOximeter/Model/Audio

So basically there are 5 classes:

  1. DataChunk
  2. FormatChunk
  3. SilenceGenerator
  4. SineGenerator
  5. WaveHeader

Here is an example of how I have used them:

List<byte> temp_bytes = new List<byte>();

WaveHeader header = new WaveHeader();
FormatChunk format = new FormatChunk();
DataChunk data_chunk = new DataChunk();

SineGenerator signal_data = new SineGenerator(440, 44100, 400);
SilenceGenerator silence_data = new SilenceGenerator(44100, 500);

data_chunk.AddSampleData(signal_data.Data, signal_data.Data);
data_chunk.AddSampleData(silence_data.Data, silence_data.Data);
data_chunk.AddSampleData(signal_data.Data, signal_data.Data);
data_chunk.AddSampleData(silence_data.Data, silence_data.Data);

header.FileLength += format.Length() + data_chunk.Length();

temp_bytes.AddRange(header.GetBytes());
temp_bytes.AddRange(format.GetBytes());
temp_bytes.AddRange(data_chunk.GetBytes());

var byte_array = temp_bytes.ToArray();
MemoryStream memory_stream = new MemoryStream(byte_array);

var current_audio_player = AudioManager.Current.CreatePlayer(memory_stream);
current_audio_player.Play();
bijington commented 1 year ago

This is awesome! Thank you for sharing the code.

I've got a project that I need to dust off soon that I wanted to achieve something like this.

davepruitt commented 1 year ago

This is awesome! Thank you for sharing the code.

I've got a project that I need to dust off soon that I wanted to achieve something like this.

I also just made one more improvement to my code (still available at the above link from my previous post). I improved the SineGenerator class so that it allows ramping up/down of the signal, thus removing any pops/clicks you can get at the beginning or ending of a sound.

borrmann commented 1 year ago

If this would be implemented, could there also be a method that reads in some standard audio files (that could be overloaded for more needed types by the user) to then write those sound waves that can be played by the audioplayer?

I guess that is kind of what the proposed code already does, but I am not sure as I am really new to Audio files.

The issue where I am coming from is that I have ogg files (receiving them via backend from WhatsApp cloud api audio messages) and they work fine on android but I can’t find a way to play them on iOS as they’re not supported OOTB.