2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?