Skip to content

Commit 3b6681b

Browse files
committed
It generates images (no spectogram logic yet)
1 parent 9b4bf15 commit 3b6681b

File tree

9 files changed

+585
-1
lines changed

9 files changed

+585
-1
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
17+
waveforms
18+
*.png
19+
*.wav

.vscode/launch.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Launch",
9+
"type": "go",
10+
"request": "launch",
11+
"mode": "auto",
12+
"program": "${workspaceFolder}",
13+
"env": {},
14+
"args": []
15+
}
16+
]
17+
}

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
# waveforms
1+
# waveforms
2+
3+
# Credits
4+
5+
- https://github.com/xigh/go-wavreader
6+
- https://github.com/xigh/spectrogram

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/slacki/waveforms
2+
3+
go 1.14

main.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"image/color"
6+
7+
"github.com/slacki/waveforms/spectogram"
8+
)
9+
10+
func main() {
11+
fmt.Println("start")
12+
13+
s, err := spectogram.NewSpectogram(&spectogram.Config{
14+
BG0: color.RGBA{33, 41, 201, 255},
15+
Width: 300,
16+
Height: 50,
17+
}, "test.wav")
18+
if err != nil {
19+
panic(err)
20+
}
21+
22+
img, err := s.Generate()
23+
if err != nil {
24+
panic(err)
25+
}
26+
27+
img.ToPNG("./test.png")
28+
}

spectogram/image.go

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package spectogram
2+
3+
import (
4+
"bufio"
5+
"image"
6+
"image/color"
7+
"image/png"
8+
"os"
9+
)
10+
11+
// Image128 implements image.Image interface and represents spectogram image
12+
// TODO: rename to Image (then outside this package it'd be spectogram.Image HOW COOL IS THAT?!?!!)
13+
type Image128 struct {
14+
set int
15+
at int
16+
pix []color.Color
17+
bounds image.Rectangle
18+
}
19+
20+
func (img *Image128) ColorModel() color.Model {
21+
return color.RGBA64Model
22+
}
23+
24+
func (img *Image128) Bounds() image.Rectangle {
25+
return img.bounds
26+
}
27+
28+
func (img *Image128) offset(x, y int) int {
29+
p := image.Point{x, y}
30+
if !p.In(img.bounds) {
31+
return -1
32+
}
33+
stride := img.bounds.Dx()
34+
my := img.bounds.Min.Y
35+
mx := img.bounds.Min.X
36+
ny := y - my
37+
nx := x - mx
38+
return ny*stride + nx
39+
}
40+
41+
func (img *Image128) At(x int, y int) color.Color {
42+
o := img.offset(x, y)
43+
if o < 0 {
44+
return color.RGBA{}
45+
}
46+
img.at++
47+
return img.pix[o]
48+
}
49+
50+
func (img *Image128) Set(x int, y int, c color.Color) {
51+
o := img.offset(x, y)
52+
if o < 0 {
53+
return
54+
}
55+
img.set++
56+
img.pix[o] = c
57+
}
58+
59+
func (img *Image128) Stats() (int, int) {
60+
return img.at, img.set
61+
}
62+
63+
// ToPNG writes image to specified path in .png format.
64+
func (img *Image128) ToPNG(file string) error {
65+
f, err := os.Create(file)
66+
if err != nil {
67+
return err
68+
}
69+
defer f.Close()
70+
71+
wr := bufio.NewWriter(f)
72+
73+
err = png.Encode(wr, img)
74+
if err != nil {
75+
return err
76+
}
77+
78+
return wr.Flush()
79+
}
80+
81+
func NewImage128(bounds image.Rectangle) *Image128 {
82+
dx := bounds.Dx()
83+
dy := bounds.Dy()
84+
sz := dx * dy
85+
return &Image128{
86+
pix: make([]color.Color, sz),
87+
bounds: bounds,
88+
}
89+
}
90+
91+
type SubImage128 struct {
92+
img *Image128
93+
bounds image.Rectangle
94+
}
95+
96+
func (sub *SubImage128) ColorModel() color.Model {
97+
return sub.ColorModel()
98+
}
99+
100+
func (sub *SubImage128) Bounds() image.Rectangle {
101+
return image.Rectangle{
102+
Min: image.Pt(0, 0),
103+
Max: image.Pt(sub.bounds.Dx(), sub.bounds.Dy()),
104+
}
105+
}
106+
107+
func (sub *SubImage128) At(x int, y int) color.Color {
108+
mx := sub.bounds.Min.X
109+
my := sub.bounds.Min.Y
110+
return sub.img.At(x+mx, y+my)
111+
}
112+
113+
func (sub *SubImage128) Set(x int, y int, c color.Color) {
114+
mx := sub.bounds.Min.X
115+
my := sub.bounds.Min.Y
116+
sub.img.Set(x+mx, y+my, c)
117+
}
118+
119+
func (img *Image128) Sub(bounds image.Rectangle) *SubImage128 {
120+
return &SubImage128{
121+
img: img,
122+
bounds: bounds,
123+
}
124+
}

spectogram/spectogram.go

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package spectogram
2+
3+
import (
4+
"image"
5+
"image/color"
6+
"image/draw"
7+
"os"
8+
9+
"github.com/slacki/waveforms/wavreader"
10+
)
11+
12+
// Config is a set of options for Spectogram
13+
type Config struct {
14+
// TODO: ?
15+
Ratio float64
16+
17+
Width uint
18+
Height uint
19+
// TODO: ?
20+
Bins uint
21+
// TODO: wtf, even square is rectangle after all?
22+
Rectangle bool
23+
24+
// TODO: no idea, but probably used when actually generating spect
25+
HideAverage bool
26+
// TODO: also tbd
27+
HideRulers bool
28+
29+
// TODO: poke around with this setting and check out what does it mean
30+
PreEmphasis float64
31+
32+
// TODO: the followint 3 options don't make any sense for me yet
33+
DFT bool
34+
LOG10 bool
35+
MAG bool
36+
37+
BG0, BG1, FG0, FG1 color.Color
38+
RulerColor color.Color
39+
}
40+
41+
// Spectogram represents a spectogram to be generated
42+
type Spectogram struct {
43+
Config *Config
44+
samples []float64
45+
File string
46+
}
47+
48+
// NewSpectogram creates new Spectogram instance and configures it with Config
49+
func NewSpectogram(c *Config, file string) (*Spectogram, error) {
50+
if c.PreEmphasis == 0 {
51+
c.PreEmphasis = 0.95
52+
}
53+
54+
s := &Spectogram{
55+
Config: c,
56+
File: file,
57+
}
58+
59+
err := s.sampleWav()
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
return s, nil
65+
}
66+
67+
func (s *Spectogram) sampleWav() error {
68+
file, err := os.Open(s.File)
69+
if err != nil {
70+
panic(err)
71+
}
72+
defer file.Close()
73+
74+
wr, err := wavreader.New(file)
75+
if err != nil {
76+
return err
77+
}
78+
79+
length := wr.Len()
80+
s.samples = make([]float64, length)
81+
for i := uint64(0); i < length; i++ {
82+
samp, err := wr.At(0, i)
83+
if err != nil {
84+
return err
85+
}
86+
s.samples[i] = float64(samp)
87+
}
88+
89+
return nil
90+
}
91+
92+
func (s *Spectogram) preEmphasis() {
93+
for i := len(s.samples) - 1; i > 0; i-- {
94+
s.samples[i] = s.samples[i] - s.Config.PreEmphasis*s.samples[i-1]
95+
}
96+
}
97+
98+
func (s *Spectogram) imageBounds() image.Rectangle {
99+
w := int(s.Config.Width)
100+
h := int(s.Config.Height)
101+
b := int(s.Config.Bins)
102+
103+
return image.Rect(-20, -20, w+20, h+40+b)
104+
}
105+
106+
// Generate generates a spectogram
107+
func (s *Spectogram) Generate() (*Image128, error) {
108+
s.preEmphasis()
109+
110+
img := NewImage128(s.imageBounds())
111+
draw.Draw(img, img.Bounds(), image.NewUniform(s.Config.BG0), image.ZP, draw.Src)
112+
113+
return img, nil
114+
}

wavreader/binary.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package wavreader
2+
3+
import (
4+
"fmt"
5+
"io"
6+
)
7+
8+
func readU8(r io.ReaderAt, off int64) (uint8, error) {
9+
b := make([]byte, 1)
10+
n, err := r.ReadAt(b, off)
11+
if err != nil {
12+
return 0, err
13+
}
14+
if n != 1 {
15+
return 0, fmt.Errorf("could not read byte at %v", err)
16+
}
17+
return b[0], nil
18+
}
19+
20+
func readU16(r io.ReaderAt, off int64) (uint16, error) {
21+
b := make([]byte, 2)
22+
n, err := r.ReadAt(b, off)
23+
if err != nil {
24+
return 0, err
25+
}
26+
if n != 2 {
27+
return 0, fmt.Errorf("could not read byte at %v", err)
28+
}
29+
return uint16(b[0]) | uint16(b[1])<<8, nil
30+
}
31+
32+
func readS16(r io.ReaderAt, off int64) (int16, error) {
33+
u16, err := readU16(r, off)
34+
if err != nil {
35+
return 0, err
36+
}
37+
return int16(u16), nil
38+
}
39+
40+
func readU32(r io.ReaderAt, off int64) (uint32, error) {
41+
b := make([]byte, 4)
42+
n, err := r.ReadAt(b, off)
43+
if err != nil {
44+
return 0, err
45+
}
46+
if n != 4 {
47+
return 0, fmt.Errorf("could not read byte at %v", err)
48+
}
49+
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24, nil
50+
}
51+
52+
func readS32(r io.ReaderAt, off int64) (int32, error) {
53+
u32, err := readU32(r, off)
54+
if err != nil {
55+
return 0, err
56+
}
57+
return int32(u32), nil
58+
}
59+
60+
func readU64(r io.ReaderAt, off int64) (uint64, error) {
61+
b := make([]byte, 8)
62+
n, err := r.ReadAt(b, off)
63+
if err != nil {
64+
return 0, err
65+
}
66+
if n != 8 {
67+
return 0, fmt.Errorf("could not read byte at %v", err)
68+
}
69+
return uint64(b[0]) | uint64(b[1])<<8 |
70+
uint64(b[2])<<16 | uint64(b[3])<<24 |
71+
uint64(b[2])<<32 | uint64(b[3])<<40 |
72+
uint64(b[2])<<48 | uint64(b[3])<<56, nil
73+
}
74+
75+
func readS64(r io.ReaderAt, off int64) (int64, error) {
76+
u64, err := readU64(r, off)
77+
if err != nil {
78+
return 0, err
79+
}
80+
return int64(u64), nil
81+
}

0 commit comments

Comments
 (0)