Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/mixer #1

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/.vscode/
/samples/
*.wav
6 changes: 6 additions & 0 deletions compressor/Compressor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package compressor

//Compressor interface for sound compressor's
type Compressor interface {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Go interfaces are usually defined in user packages. So Mixer should define what it accepts as a Compressor.

Compress(notCompressedSound float64) float64
}
65 changes: 65 additions & 0 deletions compressor/DynamicRangeCompressor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package compressor

//DynamicRangeCompressor can process operation that reduces the volume of loud sounds
//Treadshold it is a volume-level above which it is necessary to begin compression
//Attack it is a delay for begin compression after exceeding the threshold
//release it is a time while compression will work
//compretionRatio it is a coefficient by which the signal will be multiplied
type DynamicRangeCompressor struct {
threshold float64
attack int
release int
compretionRatio float64
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compressionRatio


attackTimer int
releaseTimer int

isInitialize bool
thresholHasBeenExceeded bool
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thresholdHasBeenExceeded

}

//NewDynamicRangeCompressor creates new DynamicRangeCompressor to process sounds
func NewDynamicRangeCompressor(threshold, compretionRatio float64, attack, release int) *DynamicRangeCompressor {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's convenient to use a struct instead of obscure parameter lists:

compressor.NewDynamicRangeCompressor(compressor.Options{
    Threshold: 0.8,
    CompressionRatio: 0.8,
    Attack: 200,
    Release: 1000,
})

return &DynamicRangeCompressor{
threshold: threshold,
attack: attack,
attackTimer: attack,
release: release,
compretionRatio: compretionRatio,
isInitialize: true,
}
}

//Compress sound
func (d *DynamicRangeCompressor) Compress(notCompressedSound float64) float64 {

if d == nil || !d.isInitialize {
panic("dynamicRangeCOmpressor must be initialized. Use NewCompressor function")
}

compressedSound := notCompressedSound

if notCompressedSound > d.threshold {
d.releaseTimer = d.release
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can attackTimer be set here as well?

d.thresholHasBeenExceeded = true
}

if d.attackTimer == 0 && d.releaseTimer == 0 && d.thresholHasBeenExceeded {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's possible to do this without thresholHasBeenExceeded:
decrease attackTimer until it's zero,
then if attackTimer == 0 decrease releaseTimer (and apply compressFunction) until it's zero

d.attackTimer = d.attack
d.thresholHasBeenExceeded = false
compressedSound = d.compressFunction(notCompressedSound)

} else if d.attackTimer == 0 && d.releaseTimer > 0 {
compressedSound = d.compressFunction(notCompressedSound)
d.releaseTimer--

} else if d.thresholHasBeenExceeded {
d.attackTimer--
}

return compressedSound
}

func (d *DynamicRangeCompressor) compressFunction(notCompressedSound float64) float64 {
return notCompressedSound * d.compretionRatio
}
62 changes: 62 additions & 0 deletions compressor/DynamicRangeCompressor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package compressor

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("DynamicRangeCompressor", func() {

It("Should return same value", func() {

compressor := NewDynamicRangeCompressor(2.0, 0.5, 1, 2)

Expect(0.5).To(Equal(compressor.Compress(0.5)))
Expect(0.5).To(Equal(compressor.Compress(0.5)))
Expect(0.5).To(Equal(compressor.Compress(0.5)))
Expect(0.5).To(Equal(compressor.Compress(0.5)))
Expect(0.5).To(Equal(compressor.Compress(0.5)))
Expect(0.5).To(Equal(compressor.Compress(0.5)))
Expect(0.5).To(Equal(compressor.Compress(0.5)))
})

It("Should return compresed value with attack delay", func() {
compressor := NewDynamicRangeCompressor(1.9, 0.5, 1, 2)

Expect(2.0).To(Equal(compressor.Compress(2.0)))
Expect(1.0).To(Equal(compressor.Compress(2.0)))
Expect(0.5).To(Equal(compressor.Compress(1.0)))
Expect(0.5).To(Equal(compressor.Compress(1.0)))
Expect(1.0).To(Equal(compressor.Compress(1.0)))
Expect(1.0).To(Equal(compressor.Compress(1.0)))
})

It("Should return compresed value without attack delay", func() {
compressor := NewDynamicRangeCompressor(1.9, 0.5, 0, 2)

Expect(1.0).To(Equal(compressor.Compress(2.0)))
Expect(0.5).To(Equal(compressor.Compress(1.0)))
Expect(0.5).To(Equal(compressor.Compress(1.0)))
Expect(1.0).To(Equal(compressor.Compress(1.0)))
Expect(5.0).To(Equal(compressor.Compress(10.0)))
Expect(0.5).To(Equal(compressor.Compress(1.0)))
})

It("Should return compresed value without attack delay and witout release delay", func() {
compressor := NewDynamicRangeCompressor(1.9, 0.5, 0, 0)

Expect(1.0).To(Equal(compressor.Compress(2.0)))
Expect(1.0).To(Equal(compressor.Compress(2.0)))
Expect(1.0).To(Equal(compressor.Compress(1.0)))
Expect(1.0).To(Equal(compressor.Compress(1.0)))
Expect(5.0).To(Equal(compressor.Compress(10.0)))
Expect(1.0).To(Equal(compressor.Compress(1.0)))
})
})

func TestCompressor(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Compressor Suite")
}
10 changes: 10 additions & 0 deletions compressor/MockCompressorImpl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package compressor

//MockCompressorImpl this is mock for interface Compressor. Hi does nothing.
type MockCompressorImpl struct {
}

//Compress just return same value
func (m MockCompressorImpl) Compress(notCompressedSound float64) float64 {
return notCompressedSound
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ module github.com/NIHERASE/voxcox
go 1.12

require (
github.com/go-audio/audio v1.0.0
github.com/go-audio/wav v1.0.0
github.com/jfreymuth/pulse v0.0.0-20200107133239-fe42f62ea140
github.com/karalabe/xgo v0.0.0-20191115072854-c5ccff8648a7 // indirect
github.com/labstack/gommon v0.3.0
github.com/onsi/ginkgo v1.6.0
github.com/onsi/gomega v1.9.0
gopkg.in/hraban/opus.v2 v2.0.0-20191117073431-57179dff69a6
)
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-audio/audio v1.0.0 h1:zS9vebldgbQqktK4H0lUqWrG8P0NxCJVqcj7ZpNnwd4=
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.0.0 h1:WdSGLhtyud6bof6XHL28xKeCQRzCV06pOFo3LZsFdyE=
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jfreymuth/pulse v0.0.0-20200107133239-fe42f62ea140 h1:z8Rh8GKvXMJ3sQfXYWCF5NMLnVshzqdPtjvcfyPMrgQ=
github.com/jfreymuth/pulse v0.0.0-20200107133239-fe42f62ea140/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no=
github.com/karalabe/xgo v0.0.0-20191115072854-c5ccff8648a7 h1:AYzjK/SHz6m6mg5iuFwkrAhCc14jvCpW9d6frC9iDPE=
Expand All @@ -16,16 +20,32 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/hraban/opus.v2 v2.0.0-20191117073431-57179dff69a6 h1:DijXYaSrpXmncjuTa+puGa28trQ8+jphcUJk3N9PzGY=
gopkg.in/hraban/opus.v2 v2.0.0-20191117073431-57179dff69a6/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
115 changes: 57 additions & 58 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import (
"os"

"github.com/go-audio/wav"
"gopkg.in/hraban/opus.v2"
)

func main() {
file, err := os.Open("sample.wav")
file, err := os.Open("./samples/file_example_WAV_1MG.wav")
if err != nil {
log.Fatal(err)
}
Expand All @@ -24,11 +23,11 @@ func main() {
log.Print("SourceBitDepth: ", buffer.SourceBitDepth)
log.Print("SampleBitDepth: ", decoder.SampleBitDepth())

output, err := os.OpenFile("result.wav", os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
log.Fatal(err)
}
defer output.Close()
// output, err := os.OpenFile("result.wav", os.O_RDWR|os.O_CREATE, 0755)
// if err != nil {
// log.Fatal(err)
// }
// defer output.Close()

// steps := 2
// for i := 0; i < len(buffer.Data)/steps; i++ {
Expand All @@ -41,64 +40,64 @@ func main() {
// buffer.Data[i*2+1] = buffer.Data[i*2+2]
// }

opusEncoder, err := opus.NewEncoder(buffer.PCMFormat().SampleRate, buffer.PCMFormat().NumChannels, opus.AppVoIP)
if err != nil {
log.Fatal("encoder fail ", err)
}
err = opusEncoder.SetBitrate(1024 * 8)
if err != nil {
log.Fatal("set bitrate fail ", err)
}
// opusEncoder, err := opus.NewEncoder(buffer.PCMFormat().SampleRate, buffer.PCMFormat().NumChannels, opus.AppVoIP)
// if err != nil {
// log.Fatal("encoder fail ", err)
// }
// err = opusEncoder.SetBitrate(1024 * 8)
// if err != nil {
// log.Fatal("set bitrate fail ", err)
// }

opusDecoder, err := opus.NewDecoder(buffer.PCMFormat().SampleRate, buffer.PCMFormat().NumChannels)
if err != nil {
log.Fatal("decoder fail", err)
}
// opusDecoder, err := opus.NewDecoder(buffer.PCMFormat().SampleRate, buffer.PCMFormat().NumChannels)
// if err != nil {
// log.Fatal("decoder fail", err)
// }

bytesPerFrame := buffer.PCMFormat().SampleRate * 60 / 1000
pcmInt16Buffer := make([]int16, len(buffer.Data))
for i, a := range buffer.Data {
pcmInt16Buffer[i] = int16(a)
}
coded := make([]byte, 10000)
decoded := make([]int16, bytesPerFrame)
pcmRecoded := make([]int16, 0)
// bytesPerFrame := buffer.PCMFormat().SampleRate * 60 / 1000
// pcmInt16Buffer := make([]int16, len(buffer.Data))
// for i, a := range buffer.Data {
// pcmInt16Buffer[i] = int16(a)
// }
// coded := make([]byte, 10000)
// decoded := make([]int16, bytesPerFrame)
// pcmRecoded := make([]int16, 0)

for frame := 0; frame < len(pcmInt16Buffer)/bytesPerFrame-1; frame++ {
start := frame * bytesPerFrame
pcmFrame := pcmInt16Buffer[start : start+bytesPerFrame]
// for frame := 0; frame < len(pcmInt16Buffer)/bytesPerFrame-1; frame++ {
// start := frame * bytesPerFrame
// pcmFrame := pcmInt16Buffer[start : start+bytesPerFrame]

log.Print("frame size: ", len(pcmFrame))
// log.Print("frame size: ", len(pcmFrame))

n, err := opusEncoder.Encode(pcmFrame, coded)
log.Print("encoded: ", n)
if err != nil {
log.Fatal(err)
}
// n, err := opusEncoder.Encode(pcmFrame, coded)
// log.Print("encoded: ", n)
// if err != nil {
// log.Fatal(err)
// }

n, err = opusDecoder.Decode(coded[:n], decoded)
if err != nil {
log.Fatal(err)
}
log.Print("decoded: ", n)
// n, err = opusDecoder.Decode(coded[:n], decoded)
// if err != nil {
// log.Fatal(err)
// }
// log.Print("decoded: ", n)

pcmRecoded = append(pcmRecoded, decoded[:n]...)
}
// pcmRecoded = append(pcmRecoded, decoded[:n]...)
// }

pcmRecodedInt := make([]int, len(pcmRecoded))
for i, b := range pcmRecoded {
pcmRecodedInt[i] = int(b)
}
// pcmRecodedInt := make([]int, len(pcmRecoded))
// for i, b := range pcmRecoded {
// pcmRecodedInt[i] = int(b)
// }

buffer.Data = pcmRecodedInt

encoder := wav.NewEncoder(
output,
buffer.PCMFormat().SampleRate,
buffer.SourceBitDepth,
buffer.PCMFormat().NumChannels,
1,
)
defer encoder.Close()
encoder.Write(buffer)
// buffer.Data = pcmRecodedInt

// encoder := wav.NewEncoder(
// output,
// buffer.PCMFormat().SampleRate,
// buffer.SourceBitDepth,
// buffer.PCMFormat().NumChannels,
// 1,
// )
// defer encoder.Close()
// encoder.Write(buffer)
}
Loading