LoginSignup
2
2

More than 3 years have passed since last update.

Goでサウンドプログラミング(超入門レべル)

Posted at

Nim言語でやったのを、Go言語でもやってみようと思います。
https://qiita.com/mk2/items/bc41f9dfee6669083dbb

「ド」の音を再生する

cgo経由でmmsystem.hを利用します。Goのコードですが、ほとんどCのような感じですね。

package main

// #cgo LDFLAGS: -lwinmm
// #include <stdlib.h>
// #include <windows.h>
// #include <mmsystem.h>
import "C"
import (
    "bufio"
    "fmt"
    "math"
    "os"
    "unsafe"
)

const (
    SRATE    = 44410
    PI       = 3.14159286
    B_TIME   = 1.0
    F0       = 440.0
    AMP      = 40.0
    DATA_LEN = int(SRATE * B_TIME)
)

func main() {
    var (
        hWave C.HWAVEOUT
        whdr  C.WAVEHDR
        wfe   C.WAVEFORMATEX
    )

    bWave := (*[DATA_LEN]byte)(C.malloc(C.ulonglong(DATA_LEN)))

    for cnt := 0; cnt < DATA_LEN; cnt++ {
        bWave[cnt] = byte(AMP * math.Sin(float64(2.0*PI*F0*float32(cnt)/SRATE)))
    }

    wfe.wFormatTag = C.WAVE_FORMAT_PCM
    wfe.nChannels = 1
    wfe.nSamplesPerSec = SRATE
    wfe.nAvgBytesPerSec = SRATE
    wfe.wBitsPerSample = 8
    wfe.nBlockAlign = wfe.nChannels * wfe.wBitsPerSample / 8

    C.waveOutOpen(&hWave, C.WAVE_MAPPER, &wfe, 0, 0, C.CALLBACK_NULL)

    whdr.lpData = C.LPSTR(unsafe.Pointer(bWave))
    whdr.dwBufferLength = C.ulong(DATA_LEN)
    whdr.dwFlags = C.WHDR_BEGINLOOP | C.WHDR_ENDLOOP
    whdr.dwLoops = 1

    C.waveOutPrepareHeader(hWave, &whdr, C.uint(unsafe.Sizeof(C.WAVEHDR{})))
    C.waveOutWrite(hWave, &whdr, C.uint(unsafe.Sizeof(C.WAVEHDR{})))

    reader := bufio.NewReader(os.Stdin)
    fmt.Println("Enterを押したら終了します...")
    reader.ReadString('\n')
}

MIDIファイルを作成する(おーぷんMIDIぷろじぇくと利用)

cgo経由でおーぷんMIDIぷろじぇくとが提供している、MIDIDataライブラリを利用します。DLLやヘッダファイルなどはNim言語のときのものを流用します。

これもほぼCのような感じですね。

package main

// #cgo windows LDFLAGS: -L. -lMIDIData
// #include "MIDIData.h"
import "C"

func main() {
    var midiData = C.MIDIData_Create(C.MIDIDATA_FORMAT0, 1, C.MIDIDATA_TPQNBASE, 120)
    var midiTrack = C.MIDIData_GetFirstTrack(midiData)
    C.MIDITrack_InsertTrackNameA(midiTrack, 0, C.CString("doremi"))
    C.MIDITrack_InsertTempo(midiTrack, 0, 60000000/120)
    C.MIDITrack_InsertProgramChange(midiTrack, 0, 0, 1)

    C.MIDITrack_InsertNote(midiTrack, 0, 0, 60, 100, 120)
    C.MIDITrack_InsertNote(midiTrack, 120, 0, 62, 100, 120)
    C.MIDITrack_InsertNote(midiTrack, 240, 0, 64, 100, 120)

    C.MIDITrack_InsertEndofTrack(midiTrack, 360)
    C.MIDIData_SaveAsSMFA(midiData, C.CString("doremi.midi"))
    C.MIDIData_Delete(midiData)
}

MIDIファイルを作成する(gomidi利用)

cgoを使わずに、gomidiを利用してMIDIファイルを作成します。

最初、生成するMIDIのtickをおーぷんMIDIぷろじぇくとでやった例と同じように120でやっていたのですが、短すぎておかしいなーと思っていたところ、gomidiはデフォルト960TPQN(1tickがどのくらいの秒数になるかを決める値?)でMIDIファイルを生成するらしいということがわかりました。

おーぷんMIDIぷろじぇくとの例と同じように120TPQNにできないかと思ったのですが、イマイチ設定がわからなかったので、いったん960TPQNのまま同等のファイルが生成できるよう変更しました。

package main

import (
    "fmt"
    "os"
    "path/filepath"

    "gitlab.com/gomidi/midi/writer"
)

func main() {
    dir, _ := os.Getwd()
    f := filepath.Join(dir, "smf-test.mid")

    if _, err := os.Stat(f); os.IsExist(err) {
        os.Remove(f)
    }

    err := writer.WriteSMF(f, 1, func(wr *writer.SMF) error {
        writer.TrackSequenceName(wr, "doremi")
        writer.TempoBPM(wr, 120)
        writer.ProgramChange(wr, 1)

        writer.NoteOn(wr, 60, 100)
        wr.SetDelta(960)
        writer.NoteOff(wr, 60)

        writer.NoteOn(wr, 62, 100)
        wr.SetDelta(960)
        writer.NoteOff(wr, 62)

        writer.NoteOn(wr, 64, 100)
        wr.SetDelta(960)
        writer.NoteOff(wr, 64)
        writer.EndOfTrack(wr)
        return nil
    })

    if err != nil {
        fmt.Printf("could not write SMF file %v\n", f)
        return
    }

}

ソースコード

2
2
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
2