Skip to content

Commit cc7b817

Browse files
author
rawbits
committed
+ basic synth
+ basic analysis + examples + basic player and export options + readme, license
1 parent 6424719 commit cc7b817

47 files changed

Lines changed: 3227 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@
1919

2020
# Go workspace file
2121
go.work
22+
23+
#----------------------------------------------------------------------------------------
24+
*.wav
25+
*.png
26+
*.csv

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Dániel "rawbits" Finta
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,51 @@
1-
# BitDauer
2-
Just a wave generator for my music projects
1+
# LibBitDauer v0.1
2+
3+
A simple software synthesizer package for my audio projects. Also includes some basic sound analysis tools for no reason.
4+
5+
#### Limitations:
6+
- It has some limited support for continuous generation, but currently it is only used to generate into a fixed size buffer.
7+
- The envelope is also supposed to fully support the MIDI key-trigger mechanics, but it's never been used.
8+
9+
## Features
10+
- Oscillator
11+
- Wave functions:
12+
- Sine, square, triangle, sawtooth, reverse sawtooth
13+
- Noise:
14+
- White (using built-in Go rand.Rand)
15+
- Red, pink, blue, violet (by applying filters to the white noise)
16+
- Modulators:
17+
- Envelope (ADSR with various shapes):
18+
- LERP
19+
- Ease-in, ease-out, ease-in-out
20+
- Exponential, logarithmic, inverse exponential, inverse logarithmic
21+
- S-Curve (sigmoid)
22+
- LFO (can use all wave functions above)
23+
- Filters:
24+
- Single, chain, chain of chain
25+
- Low-pass, high-pass, band-pass, notch, peaking
26+
- Musical scale LUT generator based on a base note frequency
27+
- Output options:
28+
- Export to WAV or raw (unsigned 32 bit integer) format
29+
- Play as 1 channel 44.1kHz using the [oto package](https://github.com/ebitengine/oto)
30+
31+
## Examples
32+
Some examples are provided in the cmd folder, both for the synth and the analysis tools.
33+
34+
### Genex
35+
The synth example creates an oscillator that outputs an A4 note with a customized envelope into a 2-second-long buffer. The generated sound will be played with the default sound device and saved as a WAV file.
36+
37+
### Idex
38+
Loads a WAV file and extracts the dominant frequencies to identify the waveform. It also saves the extracted data into a CSV, and generates various diagrams into PNGs. An example untuned identification is performed using the harmonic ratios and another with other metrics (bandwidth, spectral centroid).
39+
40+
## TODOs
41+
A rough list of planned features:
42+
43+
- Mixer to have multiple oscillators
44+
- PWM generator
45+
- Filter support to the oscillator
46+
- Free form modulator (multiple envelope sections with selectable easing functions)
47+
- Other filters like expander, compressor, limiter
48+
- Handle overdrive
49+
50+
## License
51+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

cmd/genex/main.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package main
2+
3+
import (
4+
"math"
5+
6+
"github.com/rawbits2010/LibBitDauer/package/player"
7+
"github.com/rawbits2010/LibBitDauer/package/record"
8+
"github.com/rawbits2010/LibBitDauer/package/synth"
9+
"github.com/rawbits2010/LibBitDauer/package/synth/buffer"
10+
"github.com/rawbits2010/LibBitDauer/package/synth/generator"
11+
"github.com/rawbits2010/LibBitDauer/package/synth/modulator/easing"
12+
)
13+
14+
func main() {
15+
16+
player, err := player.NewSoundOutput()
17+
if err != nil {
18+
panic(err)
19+
}
20+
defer player.Close()
21+
22+
player.WaitUntilReady()
23+
24+
duration := uint(2)
25+
sampleRate := uint(44100)
26+
27+
synth.GenerateFreqTableBasedOn(440)
28+
29+
osc := synth.NewOscillator(sampleRate)
30+
osc.SwitchGeneratorType(synth.OscTypeWave)
31+
osc.Wave.SetFunction(generator.SawtoothFunction)
32+
osc.Volume = 1
33+
osc.Frequency = synth.GetFreqOf(4, synth.NoteA)
34+
35+
osc.Envelope.AttackCurve = easing.NewExponential(10)
36+
osc.Envelope.ReleaseCurve = easing.NewLogarithmic(math.E)
37+
38+
osc.Envelope.SetAttackLength(duration / 2 * 1000)
39+
osc.Envelope.SetDecayLength(0)
40+
osc.Envelope.SetSustain(1)
41+
osc.Envelope.SetReleaseLength(duration / 2 * 1000)
42+
osc.UseEnvelope = true
43+
44+
buff := buffer.Generate(duration*1000, osc)
45+
46+
player.SetSamples(buff)
47+
player.Play()
48+
player.WaitUntilStops()
49+
50+
record.WriteToWav(uint32(sampleRate), buff, "out.wav")
51+
}

cmd/idex/main.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/go-audio/wav"
8+
"github.com/rawbits2010/LibBitDauer/package/assay"
9+
)
10+
11+
func main() {
12+
13+
file, err := os.Open("out.wav")
14+
if err != nil {
15+
panic(err)
16+
}
17+
defer file.Close()
18+
19+
decoder := wav.NewDecoder(file)
20+
audioBuf, err := decoder.FullPCMBuffer()
21+
if err != nil {
22+
panic(err)
23+
}
24+
25+
sampleRate := float64(audioBuf.Format.SampleRate)
26+
samples := audioBuf.AsFloatBuffer().Data
27+
28+
windowedSignal := assay.ApplyHammingWindow(samples)
29+
spectrum := assay.GetFFTForFullSample(windowedSignal)
30+
31+
frequencies, magnitudes, phases := assay.ExtractFFTResults(spectrum, sampleRate)
32+
33+
dominantFreqIdx := assay.FindDominantFrequencyIdx(magnitudes)
34+
fmt.Printf("Dominant Frequency: %.2f Hz\n", frequencies[dominantFreqIdx])
35+
36+
err = assay.ExportFFTResultsToCSV(frequencies, magnitudes, phases, "fft_spectrum.csv")
37+
if err != nil {
38+
fmt.Println("Error writing file:", err)
39+
} else {
40+
fmt.Println("FFT Spectrum exported to fft_spectrum.csv")
41+
}
42+
43+
err = assay.PlotMagnitudeAndPhase(frequencies, magnitudes, phases, "fft_spectrum.png")
44+
if err != nil {
45+
fmt.Println("Error writing file:", err)
46+
} else {
47+
fmt.Println("FFT Spectrum exported to fft_spectrum.png")
48+
}
49+
50+
threshold := 10.0
51+
peaks := assay.FindPeakIdxsWithPercentageThreshold(magnitudes, threshold)
52+
53+
fmt.Println("Found Peaks:")
54+
for _, index := range peaks {
55+
fmt.Printf("Frequency: %.2f Hz, Magnitude: %.5f\n", frequencies[index], magnitudes[index])
56+
}
57+
58+
counts := assay.GetMagnitudePercentageDistribution(magnitudes, 100)
59+
err = assay.PlotBins(counts, 0, 100, 100, "fft_percdist.png")
60+
if err != nil {
61+
fmt.Println("Error writing file:", err)
62+
} else {
63+
fmt.Println("Magnitude distribution exported to fft_percdist.png")
64+
}
65+
66+
waveform := IdentifyByHarmonicRatios(peaks, magnitudes)
67+
fmt.Printf("Identified waveform based on harmonics ratios: %s\n", waveform)
68+
69+
waveform = IdentifyWaveformWithMetrics(peaks, magnitudes, frequencies)
70+
fmt.Printf("Identified waveform with metrics: %s\n", waveform)
71+
}
72+
73+
// An example way of the identification process is to check the magnitude changes
74+
// in the harmonics.
75+
// This is only an example, peaks need to belong to a single signal. The ratios
76+
// and number of peaks are all tunable values, and they depend on the peak finding
77+
// method.
78+
func IdentifyByHarmonicRatios(peaks []int, magnitudes []float64) string {
79+
80+
harmonicRatios := make([]float64, len(peaks))
81+
for i, peakIdx := range peaks {
82+
harmonicRatios[i] = magnitudes[peakIdx] / magnitudes[0]
83+
}
84+
85+
// values needs to be tuned
86+
if len(peaks) == 1 {
87+
// only base frequency
88+
return "Sine wave"
89+
} else if len(peaks) > 5 && (len(harmonicRatios) > 1 && harmonicRatios[1] > 0.5) {
90+
// base frequency + odd harmonics (3f, 5f, 7f...).
91+
return "Square wave"
92+
} else if len(peaks) <= 5 && (len(harmonicRatios) > 1 && harmonicRatios[1] < 0.5) {
93+
// base frequency + all odd harmonics, but amplitude shows heavily decay (e.g., 1/9, 1/25).
94+
return "Triangle wave"
95+
} else {
96+
// base frequency + all harmonics (odd and even)
97+
// or we have a complex waveform
98+
return "Sawtooth or Complex waveform"
99+
}
100+
}
101+
102+
// An example way of the identification process is to check the bandwidth and
103+
// the spectral centroid of the signal.
104+
// This is only an example, peaks need to belong to a single signal. The ratios
105+
// and number of peaks are all tunable values, and they depend on the peak finding
106+
// method.
107+
func IdentifyWaveformWithMetrics(peaks []int, magnitudes, frequencies []float64) string {
108+
109+
bandwidth := assay.CalculateBandwidth(peaks, frequencies)
110+
spectralCentroid := assay.CalculateSpectralCentroid(magnitudes, frequencies)
111+
112+
// values needs to be tuned
113+
if bandwidth > 1000 && spectralCentroid > 1500 {
114+
// Wider bandwidth due to odd harmonics, with the spectral centroid shifted to higher frequencies.
115+
// High bandwidth and spectral centroid suggest a square wave.
116+
return "Square wave"
117+
} else if bandwidth < 300 && spectralCentroid < 1000 {
118+
// A sine wave has no harmonics, so its bandwidth is very small or zero.
119+
// The spectral centroid is at the fundamental frequency, as there are no higher harmonics.
120+
// If the bandwidth is small and the spectral centroid is at the fundamental, it's likely a sine wave.
121+
return "Sine wave"
122+
} else if bandwidth >= 300 && bandwidth <= 1000 {
123+
// Typically has a narrower bandwidth with harmonics decreasing with frequency.
124+
// The spectral centroid is lower than for square waves, as harmonics decrease relative to the fundamental.
125+
// If the bandwidth is medium and the spectral centroid is lower, it's likely a triangle wave.
126+
return "Triangle wave"
127+
} else {
128+
return "Complex waveform"
129+
}
130+
}

go.mod

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
module github.com/rawbits2010/LibBitDauer
2+
3+
go 1.22.0
4+
5+
toolchain go1.23.1
6+
7+
require (
8+
gioui.org v0.7.1
9+
github.com/ebitengine/oto/v3 v3.2.0
10+
github.com/youpy/go-wav v0.3.2
11+
)
12+
13+
require (
14+
gioui.org/x v0.2.0 // indirect
15+
git.sr.ht/~sbinet/gg v0.5.0 // indirect
16+
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect
17+
github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0 // indirect
18+
github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect
19+
github.com/awalterschulze/gographviz v2.0.3+incompatible // indirect
20+
github.com/campoy/embedmd v1.0.0 // indirect
21+
github.com/chewxy/hm v1.0.0 // indirect
22+
github.com/chewxy/math32 v1.11.1 // indirect
23+
github.com/go-audio/audio v1.0.0 // indirect
24+
github.com/go-audio/riff v1.0.0 // indirect
25+
github.com/go-fonts/liberation v0.3.3 // indirect
26+
github.com/go-latex/latex v0.0.0-20240709081214-31cef3c7570e // indirect
27+
github.com/go-pdf/fpdf v0.9.0 // indirect
28+
github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198 // indirect
29+
github.com/gogo/protobuf v1.3.2 // indirect
30+
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
31+
github.com/golang/protobuf v1.5.4 // indirect
32+
github.com/google/flatbuffers v24.3.25+incompatible // indirect
33+
github.com/google/uuid v1.6.0 // indirect
34+
github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 // indirect
35+
github.com/pkg/errors v0.9.1 // indirect
36+
github.com/pmezard/go-difflib v1.0.0 // indirect
37+
github.com/xtgo/set v1.0.0 // indirect
38+
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
39+
golang.org/x/mod v0.21.0 // indirect
40+
golang.org/x/tools v0.25.0 // indirect
41+
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
42+
gonum.org/v1/gonum v0.15.1 // indirect
43+
gonum.org/v1/plot v0.14.0 // indirect
44+
google.golang.org/protobuf v1.34.2 // indirect
45+
gorgonia.org/cu v0.9.6 // indirect
46+
gorgonia.org/dawson v1.2.0 // indirect
47+
gorgonia.org/gorgonia v0.9.18 // indirect
48+
gorgonia.org/tensor v0.9.24 // indirect
49+
gorgonia.org/vecf32 v0.9.0 // indirect
50+
gorgonia.org/vecf64 v0.9.0 // indirect
51+
rsc.io/pdf v0.1.1 // indirect
52+
)
53+
54+
require (
55+
gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7 // indirect
56+
gioui.org/shader v1.0.8 // indirect
57+
github.com/ebitengine/purego v0.7.0 // indirect
58+
github.com/go-audio/wav v1.1.0
59+
github.com/go-text/typesetting v0.1.1 // indirect
60+
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12
61+
github.com/youpy/go-riff v0.1.0 // indirect
62+
github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b // indirect
63+
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
64+
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 // indirect
65+
golang.org/x/image v0.20.0 // indirect
66+
golang.org/x/sys v0.25.0 // indirect
67+
golang.org/x/text v0.18.0 // indirect
68+
)

0 commit comments

Comments
 (0)