reckfellENGINE

Sound players: start, pause and position

Complete C# examples without external audio: controls, voices, and 2D/3D listeners.

On this page

Choose a player

TypePurposeWhat affects volume
AudioStreamPlayerMusic and interfacePlayer and buses; world position is ignored
AudioStreamPlayer2DA source in a 2D worldPlayer, listener distance/direction, then buses
AudioStreamPlayer3DA source in a 3D worldAttenuation model, listener, direction, additional 3D settings, then buses

An AudioStream is a resource, not an already playing voice. For a project file, assign a resource loaded with ResourceLoader.Load<AudioStream>(path) to Stream; check for null before playing. Built-in formats include WAV, Ogg Vorbis and MP3; import settings determine compression and looping. Short effects need attention to decoding latency and simultaneous voices; music needs attention to resource size and decoding cost. Do not choose a format solely by its extension.

Run the example

Add both files below to a C# project. Create a level with a Control root, attach SoundDemo.cs, build and run with F6. Restart plays a one-second tone; pause before its end, then resume. Stop stops playback without a natural-end event. No external WAV is needed.

DemoTone creates mono 16-bit PCM at 22,050 Hz with short fades at its edges. Data contains raw little-endian samples without a WAV header. LoopBegin and LoopEnd are sample indices, not seconds; for stereo they mean frames containing a channel pair. Its parameter limits and 30-second cap belong to this teaching generator.

DemoTone.cs
C#
using System;
using System.Buffers.Binary;
using Reckfell;

public static class DemoTone
{
    public static AudioStreamWav Create(float frequency = 440, double seconds = .3,
        float amplitude = .15f, bool loop = false)
    {
        const int rate = 22050;
        if (!float.IsFinite(frequency) || frequency <= 0 || frequency >= rate / 2f ||
            !double.IsFinite(seconds) || seconds < .02 || seconds > 30 ||
            !float.IsFinite(amplitude) || amplitude < 0 || amplitude > 1)
            throw new ArgumentOutOfRangeException(nameof(frequency), "Invalid teaching-tone parameters.");
        int count = (int)(seconds * rate);
        byte[] data = new byte[count * 2];
        for (int i = 0; i < count; i++)
        {
            double fade = Math.Min(1, Math.Min(i, count - 1 - i) / (rate * .005));
            short value = (short)(Math.Sin(2 * Math.PI * frequency * i / rate) * amplitude * fade * short.MaxValue);
            BinaryPrimitives.WriteInt16LittleEndian(data.AsSpan(i * 2, 2), value);
        }
        return new AudioStreamWav
        {
            Format = AudioStreamWav.FormatEnum.Format16Bits, MixRate = rate, Stereo = false,
            Data = data, LoopMode = loop ? AudioStreamWav.LoopModeEnum.Forward : AudioStreamWav.LoopModeEnum.Disabled,
            LoopBegin = 0, LoopEnd = count
        };
    }
}
SoundDemo.cs
C#
using System;
using Reckfell;

public partial class SoundDemo : Control
{
    public AudioStreamPlayer Voice { get; private set; } = null!;
    public int FinishedCount { get; private set; }
    public Label Status { get; private set; } = null!;
    private bool _ru;
    private string Text(string ru, string en) => _ru ? ru : en;

    public override void _Ready()
    {
        _ru = TranslationServer.GetLocale().StartsWith("ru", StringComparison.OrdinalIgnoreCase);
        Voice = new AudioStreamPlayer
        {
            Name = "Voice", Stream = DemoTone.Create(seconds: 1), MaxPolyphony = 1,
            PlaybackType = AudioServer.PlaybackType.Stream
        };
        AddChild(Voice);
        var layout = new VBoxContainer { Position = new Vector2(24, 24) }; AddChild(layout);
        Status = new Label { Text = Text("Готово", "Ready") }; layout.AddChild(Status);
        AddButton(layout, Text("Запустить заново", "Restart"), Restart);
        AddButton(layout, Text("Пауза", "Pause"), () => SetPaused(true));
        AddButton(layout, Text("Продолжить", "Resume"), () => SetPaused(false));
        AddButton(layout, Text("Остановить", "Stop"), StopSound);
        Voice.Finished += () => { FinishedCount++; Status.Text = Text("Завершено", "Finished"); };
    }

    private static void AddButton(VBoxContainer parent, string text, Action action)
    {
        var button = new Button { Text = text }; parent.AddChild(button); button.Pressed += action;
    }

    public void Restart()
    {
        Voice.Stop(); Voice.Play();
        Status.Text = Text("Воспроизведение", "Playing");
    }

    public void SetPaused(bool paused)
    {
        if (!Voice.IsPlaying() && !Voice.StreamPaused) return;
        Voice.StreamPaused = paused;
        Status.Text = paused ? Text("Пауза", "Paused") : Text("Воспроизведение", "Playing");
    }

    public void StopSound()
    {
        Voice.Stop(); Status.Text = Text("Остановлено", "Stopped");
    }
}

States and multiple voices

Action / checkResult in Stream mode
Play()Creates a voice at the beginning; Play(seconds) chooses a start position
StreamPaused = truePauses existing voices; IsPlaying() becomes false while HasStreamPlayback() stays true
StreamPaused = falseResumes paused voices
Stop()Stops all voices of this player without Finished
Seek(seconds)Restarts playback at a position only while the player is playing; does nothing when stopped or paused
Natural endEmits Finished; looping does not emit an end event

StreamPaused does not retain a pending pause when no voices exist. Set it after Play(). A guard such as if (!IsPlaying()) return before resuming is incorrect: IsPlaying is already false while paused. The example accounts for both states.

MaxPolyphony = 1 keeps one voice; a new Play replaces the oldest. With a larger limit, Play adds overlapping sound. Finished means one or more voices ended during an update: others can still be playing. It does not guarantee that the whole player is empty. Check HasStreamPlayback() after the event. Use separate players or a dedicated polyphonic stream for independently controlled sources.

GetStreamPlayback() returns the most recently created playback object. Keeping a reference to it does not replace checking the player's current state. Deleting a player ends its sound: a temporary explosion must live until its sound ends, or delegate playback to an owner that lives longer.

A 2D source

Keep DemoTone in the project. In a separate level, attach SpatialSound2D.cs to an Entity2D root; set a 512 × 288 viewport and run with F6. The white point is the listener and blue is the source. Arrow keys move the source: stronger left channel on the left, stronger right on the right, quieter with distance. The example uses the standard ui_left/right/up/down actions.

SpatialSound2D.cs
C#
using Reckfell;

public partial class SpatialSound2D : Entity2D
{
    public AudioListener2D Listener { get; private set; } = null!;
    public AudioStreamPlayer2D Source { get; private set; } = null!;

    public override void _Ready()
    {
        GetViewport().AudioListenerEnable2D = true;
        Listener = new AudioListener2D { Name = "Listener", Position = new Vector2(256, 144) };
        AddChild(Listener); Listener.MakeCurrent();
        Source = new AudioStreamPlayer2D
        {
            Name = "Source", Position = new Vector2(100, 144), Stream = DemoTone.Create(loop: true),
            MaxDistance = 600, Attenuation = 1, PanningStrength = 2,
            PlaybackType = AudioServer.PlaybackType.Stream
        };
        AddChild(Source); Source.Play();
    }

    public override void _Process(double delta)
    {
        Source.Position += Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down") * 160 * (float)delta;
        QueueRedraw();
    }

    public override void _Draw()
    {
        if (Source is null || Listener is null) return;
        DrawCircle(Listener.Position, 8, Colors.White);
        DrawCircle(Source.Position, 8, new Color(.15f, .35f, 1));
        DrawLine(Listener.Position, Source.Position, new Color(.4f, .4f, .4f), 1);
    }
}

Without an explicit AudioListener2D, the listener is at the center of the relevant viewport's visible region. A camera changes that region's transform. An explicit listener can attach hearing to a character. In 2D, MaxDistance uses 2D world units, usually pixels; panning also depends on viewport size and scale. Do not copy meter values from 3D.

A 3D source

In another level, attach SpatialSound3D.cs to an Entity3D root. Keep DemoTone in the project. Code creates a listener at the origin, a camera and a blue source sphere; arrow keys move it on XZ. The camera displays the world while the separate AudioListener3D defines the listening point.

SpatialSound3D.cs
C#
using Reckfell;

public partial class SpatialSound3D : Entity3D
{
    public AudioListener3D Listener { get; private set; } = null!;
    public AudioStreamPlayer3D Source { get; private set; } = null!;

    public override void _Ready()
    {
        GetViewport().AudioListenerEnable3D = true;
        Listener = new AudioListener3D { Name = "Listener" }; AddChild(Listener); Listener.MakeCurrent();
        Source = new AudioStreamPlayer3D
        {
            Name = "Source", Position = new Vector3(3, 0, -3), Stream = DemoTone.Create(loop: true),
            UnitSize = 1, MaxDistance = 30, PanningStrength = 1,
            AttenuationFilterDb = 0, PlaybackType = AudioServer.PlaybackType.Stream
        };
        AddChild(Source);
        var material = new StandardMaterial3D
        {
            ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
            AlbedoColor = new Color(.15f, .35f, 1)
        };
        Source.AddChild(new MeshInstance3D { Mesh = new SphereMesh { Radius = .2f, Height = .4f }, MaterialOverride = material });
        var camera = new Camera3D { Position = new Vector3(0, 6, 10) }; AddChild(camera);
        camera.LookAt(Vector3.Zero); camera.MakeCurrent();
        Source.Play();
    }

    public override void _Process(double delta)
    {
        Vector2 move = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
        Source.Position += new Vector3(move.X, 0, move.Y) * 4 * (float)delta;
    }
}

Here world units are treated as meters: UnitSize = 1, MaxDistance = 30. UnitSize affects the attenuation model; it does not resize objects. A wall alone does not muffle a source: collisions do not create automatic acoustic occlusion. Implement that separately, for example by checking an obstruction and changing a filter/volume. Areas can override audio buses through separate Area2D/Area3D settings.

Positional players start playback and calculate panning during physics updates. Do not expect final panning in the same statement sequence immediately after Play. The examples use PlaybackType.Stream so the engine mixer processes buses and effects. Check Sample behavior separately, particularly on Web; these examples do not test it.

Diagnostics

SymptomCheck
Resume does nothingA !IsPlaying() guard may be rejecting a paused player
Repeated sounds cut offMaxPolyphony, deleting the player, Stop before Play
An end handler runs while sound continuesOther voices in this player, or other players
A positional source is silentSame world and viewport, enabled/current listener, distance and volume
Several sources change their loop modeThey share one AudioStream; create a separate resource for different settings
Sound becomes higher and shorterPitchScale changes pitch along with speed; it is not independent time stretching

Volume categories · Timing synchronization

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.