LoginSignup
2
0

More than 3 years have passed since last update.

音楽プレイヤー作って音楽を同時ファイルしてみたい

Posted at

前回、Goで音楽再生まではできました
https://qiita.com/usk81/items/8590172a23bb71e21329

普段、ニコ動でブラウザのタブ2つ開いて違う歌い手さんで同じ曲を同時再生してデュエットする遊びをしているので、プログラムでできないかと思ってやってみました。

元のコード

// 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
}

speakerのコードを調べてみる

// 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)
}

あれ?

var (
    mu      sync.Mutex
    mixer   beep.Mixer
    samples [][2]float64
    buf     []byte
    context *oto.Context
    player  *oto.Player
    done    chan struct{}
)

structure作ってメソッドはやすだけでいいんじゃね?

修正版を作ってみる

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

beepが依存している oto のContextが同時実行に対応していないようで、できませんでした。
今回は毎日なんか作るをやっているので、そのレイヤーに手を出すのは諦めます。

とりあえずPythonでしてみた。

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)

うごいた!!

なんか悔しい... (`;ω;´)

備考

歌いはじめの位置調整とかはできてないので、音楽編集ソフトでいじっていじって調整してください

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0