tone

package
v0.0.0-...-4859f2f Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2024 License: MIT Imports: 7 Imported by: 0

Documentation

Index

Examples

Constants

View Source
const (
	// DefaultNumHarmGains is the default number of harmonic gains above the fundamental frequency
	// that are tracked for each tone.
	DefaultNumHarmGains = 20
)
View Source
const (
	// Maximum number of significant figures to use when truncating decimals
	MaxSigFigs = 6
)

Variables

This section is empty.

Functions

func NumHarmGains

func NumHarmGains(ctx context.Context) int

NumHarmGains returns the number of harmonic gains in a tone for this context, or 0 if no value is set.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	numHarmGains := tone.NumHarmGains(ctx)

	fmt.Println(numHarmGains)

}
Output:

20

func SetNumHarmGains

func SetNumHarmGains(rate int)

SetNumHarmGains sets the global number of harmonic gains in a tone. All contexts created after this is called will use the value set here. The rate cannot be a negative numbers.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	for _, n := range []int{10, 100, tone.DefaultNumHarmGains} {
		tone.SetNumHarmGains(n)
		ctx := context.NewContext()

		numHarmGains := tone.NumHarmGains(ctx)

		fmt.Println(numHarmGains)
	}

}
Output:

10
100
20

func Trunc

func Trunc(f float32, n int) float32

Trunc truncates a decimal to have no more than n digits.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/tone"
)

func main() {
	frequencies := []float32{0.123456789, 0.987654321, 99.99}
	for _, f1 := range frequencies {
		f2 := tone.Trunc(f1, 3)
		f3 := tone.Trunc(f1, tone.MaxSigFigs)

		fmt.Println(f1, f2, f3)
	}

}
Output:

0.12345679 0.123 0.123456
0.9876543 0.987 0.987654
99.99 99.9 99.99

Types

type Tone

type Tone struct {
	// Frequency is the tone's fundamental frequency.
	Frequency float32

	// Gain is a multiplier that increases or decreases the amplitude of the tone. It is a ratio of
	// the amplitude of the output signal to the amplitude of the base signal.
	//
	// As an example, a value of 2 will double the signal's strength, while a value of 0.5 will
	// halve it.
	Gain float32

	// HarmonicGains is a list of gains for each harmonic above the fundamental frequency. They are
	// ratios of the amplitude of the harmonic to the amplitude of the fundamental frequency. The
	// first element is the gain of the first harmonic (which has a frequency twice as high as the
	// fundamental frequency), the second element is the gain of the second harmonic (three times as
	// high), and so on.
	//
	// As an example, a value of 2 will double the amplitude of that harmonic relative to the
	// fundamental frequency, while a value of 0.5 will halve it.
	HarmonicGains []float32
}

A Tone represents a single tone, which is a fundamental frequency and its harmonics at regular intervals above it. A new tone must be created with NewTone before it can be used.

func NewSawtoothTone

func NewSawtoothTone(ctx context.Context, frequency float32) Tone

NewSawtoothTone creates a new tone that has a sawtooth waveform.

In a sawtooth waveform, each harmonic has a gain that is the inverse of its order. Because it is composed of every harmonic of the fundamental frequency, a sawtooth waveform is brighter and richer than other waveforms.

For example, a sawtooth tone that has a fundamental frequency (order=1) of 100Hz and a gain of 1 has these first four harmonics:

  • Harmonic 1: order = 2, frequency = 200Hz, gain = 1/2
  • Harmonic 2: order = 3, frequency = 300Hz, gain = 1/3
  • Harmonic 3: order = 4, frequency = 400Hz, gain = 1/4
  • Harmonic 4: order = 5, frequency = 500Hz, gain = 1/5
Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	sawTone := tone.NewSawtoothTone(ctx, 440)

	fmt.Println(sawTone.Frequency, sawTone.Gain, sawTone.HarmonicGains)

	// Show the calculation for each harmonic gain.
	// gain = 1 / order
	gains := make([]float32, len(sawTone.HarmonicGains))
	for i := range gains {
		gains[i] = tone.Trunc(1/float32(i+2), tone.MaxSigFigs)
	}

	fmt.Println(gains)

}
Output:

440 0 [0.5 0.333333 0.25 0.2 0.166666 0.142857 0.125 0.111111 0.1 0.090909 0.0833333 0.076923 0.0714285 0.0666666 0.0625 0.0588235 0.0555555 0.0526315 0.05 0.047619]
[0.5 0.333333 0.25 0.2 0.166666 0.142857 0.125 0.111111 0.1 0.090909 0.0833333 0.076923 0.0714285 0.0666666 0.0625 0.0588235 0.0555555 0.0526315 0.05 0.047619]

func NewSquareTone

func NewSquareTone(ctx context.Context, frequency float32) Tone

NewSquareTone creates a new tone that has a square waveform.

In a square waveform, the even-ordered harmonics have no gain, while the odd-ordered harmonics have gains that are the inverse of their orders. This waveform has a duty cycle of 50%, meaning that is has equal high and low periods.

For example, a square tone that has a fundamental frequency (order=1) of 100Hz and a gain of 1 has these first four harmonics:

  • Harmonic 1: order = 2, frequency = 200Hz, gain = 0
  • Harmonic 2: order = 3, frequency = 300Hz, gain = 1/3
  • Harmonic 3: order = 4, frequency = 400Hz, gain = 0
  • Harmonic 4: order = 5, frequency = 500Hz, gain = 1/5
Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	sqrTone := tone.NewSquareTone(ctx, 440)

	fmt.Println(sqrTone.Frequency, sqrTone.Gain, sqrTone.HarmonicGains)

	// Show the calculation for each harmonic gain.
	// gain = 1 / order if order is odd, 0 if order is even
	gains := make([]float32, len(sqrTone.HarmonicGains))
	for i := range gains {
		if i%2 > 0 {
			gains[i] = tone.Trunc(1/float32(i+2), tone.MaxSigFigs)
		}
	}

	fmt.Println(gains)

}
Output:

440 0 [0 0.333333 0 0.2 0 0.142857 0 0.111111 0 0.090909 0 0.076923 0 0.0666666 0 0.0588235 0 0.0526315 0 0.047619]
[0 0.333333 0 0.2 0 0.142857 0 0.111111 0 0.090909 0 0.076923 0 0.0666666 0 0.0588235 0 0.0526315 0 0.047619]

func NewTone

func NewTone(ctx context.Context) Tone

NewTone initializes a tone with default/zero values.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	tone := tone.NewTone(ctx)

	fmt.Println(tone.Frequency, tone.Gain, tone.HarmonicGains)

}
Output:

0 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

func NewToneAt

func NewToneAt(ctx context.Context, frequency float32) Tone

NewToneAt initializes a tone with the specified fundamental frequency.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	tone := tone.NewToneAt(ctx, 523.2511)

	fmt.Println(tone.Frequency, tone.Gain, tone.HarmonicGains)

}
Output:

523.2511 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

func NewToneFrom

func NewToneFrom(ctx context.Context, note note.Note, octave int) Tone

NewToneFrom initializes a tone from the specified note and octave.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/note"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	tone := tone.NewToneFrom(ctx, note.C, 5)

	fmt.Println(tone.Frequency, tone.Gain, tone.HarmonicGains)

}
Output:

523.2511 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

func NewToneWith

func NewToneWith(ctx context.Context, frequency float32, gain float32, harmonicGains []float32) Tone

NewToneWith initializes a tone with the specified fundamental frequency, gain, and harmonic gains. The harmonic gains are set directly in the tone, as opposed to allocating a new slice and copying over the values. If the number of harmonic gains provided does not match the number for the context, this grows or shrinks the slice to match the expected length.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	for _, harmGains := range [][]float32{
		{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0},
		{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0},
		{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0},
	} {
		tone := tone.NewToneWith(ctx, 440, 1, harmGains)

		fmt.Println(tone.Frequency, tone.Gain, tone.HarmonicGains)
	}

}
Output:

440 1 [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 0 0 0 0 0 0 0 0 0 0]
440 1 [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2]
440 1 [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2]

func NewTriangleTone

func NewTriangleTone(ctx context.Context, frequency float32) Tone

NewTriangleTone creates a new tone that has a triangle waveform.

In a triangle waveform, the even-ordered harmonics have no gain, while the odd-ordered harmonics have gains that are the inverse square of their orders (which also makes this waveform an integral of a square waveform). This leads to a much steeper roll-off than other waveforms.

For example, a triangle tone that has a fundamental frequency (order=1) of 100Hz and a gain of 1 has these first four harmonics:

  • Harmonic 1: order = 2, frequency = 200Hz, gain = 0
  • Harmonic 2: order = 3, frequency = 300Hz, gain = 1/9
  • Harmonic 3: order = 4, frequency = 400Hz, gain = 0
  • Harmonic 4: order = 5, frequency = 500Hz, gain = 1/25
Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	triTone := tone.NewTriangleTone(ctx, 440)

	fmt.Println(triTone.Frequency, triTone.Gain, triTone.HarmonicGains)

	// Show the calculation for each harmonic gain.
	// gain = 1 / order^2 if order is odd, 0 if order is even
	gains := make([]float32, len(triTone.HarmonicGains))
	for i := range gains {
		if i%2 > 0 {
			gains[i] = tone.Trunc(1/float32((i+2)*(i+2)), tone.MaxSigFigs)
		}
	}

	fmt.Println(gains)

}
Output:

440 0 [0 0.111111 0 0.04 0 0.0204081 0 0.0123456 0 0.00826446 0 0.00591716 0 0.00444444 0 0.0034602 0 0.00277008 0 0.00226757]
[0 0.111111 0 0.04 0 0.0204081 0 0.0123456 0 0.00826446 0 0.00591716 0 0.00444444 0 0.0034602 0 0.00277008 0 0.00226757]

func (*Tone) Clone

func (tone *Tone) Clone() Tone

Clone returns a complete copy of the tone that has all of the same values as the original but does not share any memory with it.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	tone := tone.NewToneWith(ctx, 523.2511, 0.5, []float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0})
	clone := tone.Clone()

	tone.Frequency = 440
	tone.Gain = 1
	tone.HarmonicGains[0] = 100
	tone.HarmonicGains[1] = 200
	tone.HarmonicGains[2] = 300

	fmt.Println(tone.Frequency, tone.Gain, tone.HarmonicGains)
	fmt.Println(clone.Frequency, clone.Gain, clone.HarmonicGains)

}
Output:

440 1 [100 200 300 0.4 0.5 0.6 0.7 0.8 0.9 1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2]
523.2511 0.5 [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2]

func (*Tone) Empty

func (tone *Tone) Empty() bool

Empty checks whether the tone does not have any values set.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	tone := tone.NewTone(ctx)
	isEmpty1 := tone.Empty()

	tone.Frequency = 440
	isEmpty2 := tone.Empty()

	tone.Frequency, tone.Gain = 0, 1
	isEmpty3 := tone.Empty()

	fmt.Println(isEmpty1, isEmpty2, isEmpty3)

}
Output:

true false false

func (*Tone) HarmonicFreq

func (tone *Tone) HarmonicFreq(order int) float32

HarmonicFreq calculates the frequency of one of the tone's harmonic. The fundamental frequency has an order of 1. The frequency is truncated to have no more than MaxSigFigs digits.

As an example, if the tone has a fundamental frequency of 440Hz, then the first harmonic (order=2) is 880Hz and the second harmonic (order=3) is 1320Hz,

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	tone := tone.NewTone(ctx)
	harmFreq1 := tone.HarmonicFreq(2)

	tone.Frequency = 440

	harmFreq2 := tone.HarmonicFreq(1)
	harmFreq3 := tone.HarmonicFreq(2)
	harmFreq4 := tone.HarmonicFreq(3)

	fmt.Println(harmFreq1, harmFreq2, harmFreq3, harmFreq4)

}
Output:

0 440 880 1320

func (*Tone) Reset

func (tone *Tone) Reset()

Reset resets the tone to its zero values. The harmonic gains are set to zero, but the slice header does not change.

Example
package main

import (
	"fmt"

	"github.com/green-aloe/enobox/context"
	"github.com/green-aloe/enobox/tone"
)

func main() {
	ctx := context.NewContext()

	tone := tone.NewSquareTone(ctx, 440)

	fmt.Println(tone.Frequency, tone.Gain, tone.HarmonicGains)

	tone.Reset()

	fmt.Println(tone.Frequency, tone.Gain, tone.HarmonicGains)

}
Output:

440 0 [0 0.333333 0 0.2 0 0.142857 0 0.111111 0 0.090909 0 0.076923 0 0.0666666 0 0.0588235 0 0.0526315 0 0.047619]
0 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

Jump to

Keyboard shortcuts

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