resound

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 4, 2023 License: MIT Imports: 4 Imported by: 1

README ¶

Resound 🔉

Resound is a library for applying sound effects when playing sounds back with Ebitengine. Resound was made primarily for game development, as you might expect.

Why did you make this?

C'mon man, you already know what it is

The general advantages of using Resound is two-fold. Firstly, it allows you to easily add non-standard effects (like low-pass filtering, distortion, or panning) to sound or music playback. Secondly, it allows you to easily apply these effects across multiple groups of sounds, like a DSP. The general idea of using buses / channels is, again, taken from how Godot does things, along with other DAWs and music creation tools, like Renoise, Reason, and SunVox.

How do I use it?

There's a couple of different ways.

  1. Create effects and play an audio stream through them. The effects themselves satisfy io.ReadSeeker, like an ordinary audio stream from Ebitengine, so you can chain them together.

// Let's assume our sound is read in or embedded as a series of bytes.
var soundBytes []byte

const sampleRate = 44100

func main() {

    // So first, we'll create an audio context, decode some bytes into a stream,
    // create a loop, etc. 
    context := audio.NewContext(sampleRate)

    reader := bytes.NewReader(soundBytes)

    stream, err := vorbis.DecodeWithSampleRate(sampleRate, reader)

    if err != nil {
        panic(err)
    }

    loop := audio.NewInfiniteLoop(stream, stream.Length())

    // But here, we'll create a Delay effect and apply it.
    delay := effects.NewDelay(loop).SetWait(0.1).SetStrength(0.2)

    // Effects in Resound wrap streams (including other effects), so you can just use them
    // like you would an ordinary audio stream in Ebitengine.

    // You can also easily chain effects by using resound.ChainEffects().

    // Now we create a new player of the loop + delay:
    player, err := context.NewPlayer(delay)

    // Note that if you're going to change effect parameters in real time, you may want to lower the internal buffer size for Players using (*audio.Player).SetBufferSize()

    if err != nil {
        panic(err)
    }

    // Play it, and you're good to go.
    player.Play()


}

  1. Apply effects to a DSP Channel, and then play sounds through there. This allows you to automatically play sounds back using various shared properties (a shared volume, shared panning, shared filter, etc).

// Let's, again, assume our sound is read in or embedded as a series of bytes.
var soundBytes []byte

// Here, though, we'll be creating a DSPChannel.
var dsp *resound.DSPChannel

const sampleRate = 44100

func main() {

    // So first, we'll create an audio context, decode some bytes into a stream,
    // create a loop, etc. 
    audio.NewContext(sampleRate)

    reader := bytes.NewReader(soundBytes)

    stream, err := vorbis.DecodeWithSampleRate(sampleRate, reader)

    if err != nil {
        panic(err)
    }

    loop := audio.NewInfiniteLoop(stream, stream.Length())

    // But here, we create a DSPChannel. A DSPChannel represents a group of effects
    // that sound streams play through. When playing a stream through a DSPChannel,
    // the stream takes on the effects applied to the DSPChannel. We don't have to
    // pass a stream to effects when used with a DSPChannel, because every stream
    // played through the channel takes the effect.
    dsp = resound.NewDSPChannel()
    dsp.AddEffect("delay", effects.NewDelay(nil).SetWait(0.1).SetStrength(0.25))
    dsp.AddEffect("distort", effects.NewDistort(nil).SetStrength(0.25))
    dsp.AddEffect("volume", effects.NewVolume(nil).SetStrength(0.25))

    // Now we create a new player from the DSP channel. This will return a
    // *resound.ChannelPlayback object, which works similarly to an audio.Player
    // (in fact, it embeds the *audio.Player).
    player := dsp.CreatePlayer(loop)

    // Play it, and you're good to go, again - this time, it will run its playback
    // through the effect stack in the DSPChannel, in this case Delay > Distort > Volume.
    player.Play()

}

To-do

  • Global Stop - Tracking playing sounds to globally stop all sounds that are playing back
  • DSPChannel Stop - ^, but for a DSP channel
  • Volume normalization - done through the AudioProperties struct.
  • Beat / rhythm analysis?
  • Replace all usage of "strength" with "wet/dry".
Effects
  • Volume
  • Pan
  • Delay
  • Distortion
  • Low-pass Filter
  • Bitcrush (?)
  • High-pass Filter
  • Reverb
  • Mix / Fade (between two streams, or between a stream and silence, and over a customizeable time)
  • Loop (like, looping a signal after so much time has passed or the signal ends)
  • Pitch shifting
  • Playback speed adjustment
  • 3D Sound (quick and easy panning and volume adjustment based on distance from listener to source)
Generators
  • Silence

  • Static

    ... And whatever else may be necessary.

Known Issues

  • Currently, effects directly apply on top of streams, which means that any effects that could make streams longer (like reverbs or delays) will get cut off if the source stream ends.

Documentation ¶

Index ¶

Constants ¶

This section is empty.

Variables ¶

This section is empty.

Functions ¶

This section is empty.

Types ¶

type AnalysisResult ¶

type AnalysisResult struct {
	Normalization float64
}

AnalysisResult is an object that contains the results of an analysis performed on a stream.

type AudioBuffer ¶

type AudioBuffer []byte

AudioBuffer wraps a []byte of audio data and provides handy functions to get and set values for a specific position in the buffer.

func (AudioBuffer) Get ¶

func (ab AudioBuffer) Get(i int) (l, r float64)

Get returns the values for the left and right audio channels at the specified stream sample index. The values returned for the left and right audio channels range from 0 to 1.

func (AudioBuffer) Len ¶

func (ab AudioBuffer) Len() int

func (AudioBuffer) Set ¶

func (ab AudioBuffer) Set(i int, l, r float64)

Set sets the left and right audio channel values at the specified stream sample index. The values should range from 0 to 1.

func (AudioBuffer) String ¶

func (ab AudioBuffer) String() string

type AudioProperties ¶

type AudioProperties map[string]*AudioProperty

func NewAudioProperties ¶

func NewAudioProperties() AudioProperties

func (AudioProperties) Get ¶

func (ap AudioProperties) Get(name string) *AudioProperty

type AudioProperty ¶

type AudioProperty struct {
	// contains filtered or unexported fields
}

AudioProperty is an object that allows associating an AnalysisResult for a specific stream with a name for that stream.

func (*AudioProperty) Analyze ¶

func (ap *AudioProperty) Analyze(stream io.ReadSeeker, scanCount int64) AnalysisResult

Analyze analyzes the provided audio stream, returning an AnalysisResult object. The stream is the audio stream to be used for scanning, and the scanCount is the number of times the function should scan various parts of the audio stream. The higher the scan count, the more accurate the results should be, but the longer the scan would take. A scanCount of 16 means it samples the stream 16 times evenly throughout the file. If a scanCount of 0 is provided, it will default to 16.

func (*AudioProperty) ResetAnalyzation ¶

func (ap *AudioProperty) ResetAnalyzation()

type DSPChannel ¶

type DSPChannel struct {
	Active      bool
	Effects     map[string]IEffect
	EffectOrder []IEffect
}

DSPChannel represents a channel that can have various effects applied to it.

func NewDSPChannel ¶

func NewDSPChannel() *DSPChannel

NewDSPChannel returns a new DSPChannel.

func (*DSPChannel) Add ¶

func (dsp *DSPChannel) Add(name string, effect IEffect) *DSPChannel

Add adds the specified Effect to the DSPChannel under the given name. Note that effects added to DSPChannels don't need to specify source streams, as the DSPChannel automatically handles this.

func (*DSPChannel) CreatePlayer ¶

func (dsp *DSPChannel) CreatePlayer(sourceStream io.ReadSeeker) *DSPPlayer

CreatePlayer creates a new DSPPlayer to handle playback of a stream through the DSPChannel.

type DSPPlayer ¶

type DSPPlayer struct {
	*audio.Player
	Channel *DSPChannel
	Source  io.ReadSeeker
}

DSPPlayer embeds audio.Player and so has all of the functions and abilities of the default audio.Player while also applying effects placed on the source DSPChannel.

func (*DSPPlayer) Clone ¶

func (es *DSPPlayer) Clone() *DSPPlayer

Clone duplicates the DSPPlayer; note that the current playback values will not be cloned.

func (*DSPPlayer) Read ¶

func (es *DSPPlayer) Read(p []byte) (n int, err error)

func (*DSPPlayer) Seek ¶

func (es *DSPPlayer) Seek(offset int64, whence int) (int64, error)

type IEffect ¶

type IEffect interface {
	io.ReadSeeker
	ApplyEffect(data []byte, bytesRead int) // This function is called when sound data goes through an effect. The effect should modify the data byte buffer.
	SetSource(io.ReadSeeker)                // This function allows an effect's source to be dynamically altered; this allows for easy chaining with resound.ChainEffects().
}

IEffect indicates an effect that implements io.ReadSeeker and generally takes effect on an existing audio stream. It represents the result of applying an effect to an audio stream, and is playable in its own right.

func ChainEffects ¶

func ChainEffects(source io.ReadSeeker, effects ...IEffect) IEffect

ChainEffects chains multiple effects for you automatically, returning the last chained effect. Example: sfxChain := resound.Chain(sourceSound,

resound.NewDelay(nil).SetWait(0.2).SetStrength(0.5),
resound.NewPan(nil),
resound.NewVolume(nil),

) sfxChain at the end would be the Volume effect, which is being fed by the Pan effect, which is fed by the Delay effect.

Directories ¶

Path Synopsis
examples
dsp

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL