Last time, I was able to play music with Go https://qiita.com/usk81/items/8590172a23bb71e21329
I usually play Nico Douga by opening two browser tabs and playing the same song at the same time with different singers and doing a duet, so I thought I could do it with a program.
// package main plays two audio file
package main
import (
"log"
"os"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
)
func main() {
f, err := os.Open("test.mp3")
if err != nil {
log.Fatal(err)
}
st, format, err := mp3.Decode(f)
if err != nil {
log.Fatal(err)
}
defer st.Close()
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan bool)
speaker.Play(beep.Seq(st, beep.Callback(func() {
done <- true
})))
<-done
}
// Package speaker implements playback of beep.Streamer values through physical speakers.
package speaker
import (
"sync"
"github.com/faiface/beep"
"github.com/hajimehoshi/oto"
"github.com/pkg/errors"
)
var (
mu sync.Mutex
mixer beep.Mixer
samples [][2]float64
buf []byte
context *oto.Context
player *oto.Player
done chan struct{}
)
// Init initializes audio playback through speaker. Must be called before using this package.
//
// The bufferSize argument specifies the number of samples of the speaker's buffer. Bigger
// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better
// responsiveness and less delay.
func Init(sampleRate beep.SampleRate, bufferSize int) error {
mu.Lock()
defer mu.Unlock()
Close()
mixer = beep.Mixer{}
numBytes := bufferSize * 4
samples = make([][2]float64, bufferSize)
buf = make([]byte, numBytes)
var err error
context, err = oto.NewContext(int(sampleRate), 2, 2, numBytes)
if err != nil {
return errors.Wrap(err, "failed to initialize speaker")
}
player = context.NewPlayer()
done = make(chan struct{})
go func() {
for {
select {
default:
update()
case <-done:
return
}
}
}()
return nil
}
// Close closes the playback and the driver. In most cases, there is certainly no need to call Close
// even when the program doesn't play anymore, because in properly set systems, the default mixer
// handles multiple concurrent processes. It's only when the default device is not a virtual but hardware
// device, that you'll probably want to manually manage the device from your application.
func Close() {
if player != nil {
if done != nil {
done <- struct{}{}
done = nil
}
player.Close()
context.Close()
player = nil
}
}
// Lock locks the speaker. While locked, speaker won't pull new data from the playing Stramers. Lock
// if you want to modify any currently playing Streamers to avoid race conditions.
//
// Always lock speaker for as little time as possible, to avoid playback glitches.
func Lock() {
mu.Lock()
}
// Unlock unlocks the speaker. Call after modifying any currently playing Streamer.
func Unlock() {
mu.Unlock()
}
// Play starts playing all provided Streamers through the speaker.
func Play(s ...beep.Streamer) {
mu.Lock()
mixer.Add(s...)
mu.Unlock()
}
// Clear removes all currently playing Streamers from the speaker.
func Clear() {
mu.Lock()
mixer.Clear()
mu.Unlock()
}
// update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the
// data is sent and started playing.
func update() {
mu.Lock()
mixer.Stream(samples)
mu.Unlock()
for i := range samples {
for c := range samples[i] {
val := samples[i][c]
if val < -1 {
val = -1
}
if val > +1 {
val = +1
}
valInt16 := int16(val * (1<<15 - 1))
low := byte(valInt16)
high := byte(valInt16 >> 8)
buf[i*4+c*2+0] = low
buf[i*4+c*2+1] = high
}
}
player.Write(buf)
}
that?
var (
mu sync.Mutex
mixer beep.Mixer
samples [][2]float64
buf []byte
context *oto.Context
player *oto.Player
done chan struct{}
)
Isn't it just a matter of making a structure and making a method easy?
package speaker
import (
"log"
"sync"
"github.com/faiface/beep"
"github.com/hajimehoshi/oto"
"github.com/pkg/errors"
)
type Player struct {
mu sync.Mutex
mixer beep.Mixer
samples [][2]float64
buf []byte
context *oto.Context
player *oto.Player
done chan struct{}
}
// Init initializes audio playback through speaker. Must be called before using this package.
//
// The bufferSize argument specifies the number of samples of the speaker's buffer. Bigger
// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better
// responsiveness and less delay.
func Init(sampleRate beep.SampleRate, bufferSize int) (p *Player, err error) {
p = &Player{}
p.mu.Lock()
defer p.mu.Unlock()
p.Close()
p.mixer = beep.Mixer{}
numBytes := bufferSize * 4
p.samples = make([][2]float64, bufferSize)
p.buf = make([]byte, numBytes)
p.context, err = oto.NewContext(int(sampleRate), 2, 2, numBytes)
if err != nil {
return nil, errors.Wrap(err, "failed to initialize speaker")
}
log.Print("before NewPlayer")
p.player = p.context.NewPlayer()
log.Print("before NewPlayer")
p.done = make(chan struct{})
log.Print("make channel")
go func() {
for {
select {
default:
p.update()
case <-p.done:
return
}
}
}()
return p, nil
}
// Close closes the playback and the driver. In most cases, there is certainly no need to call Close
// even when the program doesn't play anymore, because in properly set systems, the default mixer
// handles multiple concurrent processes. It's only when the default device is not a virtual but hardware
// device, that you'll probably want to manually manage the device from your application.
func (p *Player) Close() {
if p.player != nil {
if p.done != nil {
p.done <- struct{}{}
p.done = nil
}
p.player.Close()
p.context.Close()
p.player = nil
}
}
// Lock locks the speaker. While locked, speaker won't pull new data from the playing Stramers. Lock
// if you want to modify any currently playing Streamers to avoid race conditions.
//
// Always lock speaker for as little time as possible, to avoid playback glitches.
func (p *Player) Lock() {
p.mu.Lock()
}
// Unlock unlocks the speaker. Call after modifying any currently playing Streamer.
func (p *Player) Unlock() {
p.mu.Unlock()
}
// Play starts playing all provided Streamers through the speaker.
func (p *Player) Play(s ...beep.Streamer) {
p.mu.Lock()
p.mixer.Add(s...)
p.mu.Unlock()
}
// Clear removes all currently playing Streamers from the speaker.
func (p *Player) Clear() {
p.mu.Lock()
p.mixer.Clear()
p.mu.Unlock()
}
// update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the
// data is sent and started playing.
func (p *Player) update() {
p.mu.Lock()
p.mixer.Stream(p.samples)
p.mu.Unlock()
// buf := p.buf
for i := range p.samples {
for c := range p.samples[i] {
val := p.samples[i][c]
if val < -1 {
val = -1
}
if val > +1 {
val = +1
}
valInt16 := int16(val * (1<<15 - 1))
low := byte(valInt16)
high := byte(valInt16 >> 8)
p.buf[i*4+c*2+0] = low
p.buf[i*4+c*2+1] = high
}
}
p.player.Write(p.buf)
}
// package main plays two audio file
// failure: oto.NewContext can be called only once
package main
import (
"log"
"os"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/usk81/til/go-duet-player/speaker"
)
func main() {
f1, err := os.Open("test1.mp3")
if err != nil {
log.Fatal(err)
}
st1, format, err := mp3.Decode(f1)
if err != nil {
log.Fatal(err)
}
defer st1.Close()
sp1, err := speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
if err != nil {
log.Fatal(err)
}
done1 := make(chan bool)
f2, err := os.Open("test2.mp3")
if err != nil {
log.Fatal(err)
}
st2, format, err := mp3.Decode(f2)
if err != nil {
log.Fatal(err)
}
defer st2.Close()
sp2, err := speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
if err != nil {
log.Fatal(err)
}
done2 := make(chan bool)
sp1.Play(beep.Seq(st1, beep.Callback(func() {
done1 <- true
})))
sp2.Play(beep.Seq(st2, beep.Callback(func() {
done2 <- true
})))
<-done1
<-done2
}
oto: NewContext can be called only once
I couldn't because the Context of ʻoto` on which beep depends doesn't seem to support concurrency. This time I'm making something every day, so I'll give up on that layer.
from pydub import AudioSegment
from pydub.playback import play
#Load an audio file
audio1 = "test1.mp3"
audio2 = "test2.mp3"
sound1 = AudioSegment.from_mp3(audio1)
sound2 = AudioSegment.from_mp3(audio2)
combined = sound1.overlay(sound2)
play(combined)
It moved! !!
Somehow regrettable ... (`; ω; ´)
It is not possible to adjust the position at the beginning of singing, so please adjust it by playing with music editing software.
Recommended Posts