/
mikmod.go
217 lines (178 loc) · 5.37 KB
/
mikmod.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// Package mikmod lets you use MikMod from Go.
package mikmod
/*
#cgo LDFLAGS: -lmikmod
#include <mikmod.h>
*/
import "C"
import (
"errors"
"sync"
"time"
"unsafe"
)
// Return MikMod library version.
func Version() (major int, minor int, rev int) {
v := C.MikMod_GetVersion()
rev = int(v & 0xFF)
minor = int((v >> 8) & 0xFF)
major = int((v >> 16) & 0xFF)
return
}
// Init initializes the MikMod library. Make sure to call Uninit when
// done.
func Init() error {
C.MikMod_InitThreads()
C.MikMod_RegisterAllDrivers()
C.MikMod_RegisterAllLoaders()
C.md_mode = C.DMODE_SOFT_MUSIC | C.DMODE_NOISEREDUCTION
initString := mikmodString("")
defer C.free(unsafe.Pointer(initString))
if err := int(C.MikMod_Init(initString)); err != 0 {
return mikmodError()
}
return nil
}
// Uninit uninitializes the MikMod library.
func Uninit() {
Stop()
C.MikMod_Exit()
}
// Module represents a MikMod module. Remember to Close it when done.
type Module struct {
module *C.MODULE
}
// LoadModuleFromFile attempts to load a MikMod module from the file
// designated by filename.
func LoadModuleFromFile(filename string) (*Module, error) {
fn := mikmodString(filename)
defer C.free(unsafe.Pointer(fn))
module := C.Player_Load(fn, 128, C.BOOL(0))
if module == nil {
return nil, mikmodError()
}
return &Module{module}, nil
}
// LoadModuleFromSlice attempts to load a MikMod module from the
// supplied byte slice.
func LoadModuleFromSlice(b []byte) (*Module, error) {
module := C.Player_LoadMem((*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)), 128, C.BOOL(0))
if module == nil {
return nil, mikmodError()
}
return &Module{module}, nil
}
// Title returns the module's song name.
func (m *Module) Title() string { return C.GoString((*C.char)(m.module.songname)) }
// NumChannels returns the number of channels used by the module.
func (m *Module) NumChannels() int { return int(m.module.numchn) }
// NumVoices returns the number of voices reserved by the player for
// real and virtual channels.
func (m *Module) NumVoices() int { return int(m.module.numvoices) }
// NumPositions returns the number of song positions.
func (m *Module) NumPositions() int { return int(m.module.numpos) }
// NumPatterns returns the number of song patterns.
func (m *Module) NumPatterns() int { return int(m.module.numpat) }
// NumInstruments returns the number of instruments in the module.
func (m *Module) NumInstruments() int { return int(m.module.numins) }
// NumSamples returns the number of samples in the module.
func (m *Module) NumSamples() int { return int(m.module.numsmp) }
// Tracker returns the name of the tracker used to create the song.
func (m *Module) Tracker() string { return C.GoString((*C.char)(m.module.modtype)) }
// Comment returns the song comment.
func (m *Module) Comment() string { return C.GoString((*C.char)(m.module.comment)) }
// Elapsed returns the time elapsed since the song started playing.
func (m *Module) Elapsed() time.Duration {
return time.Duration(m.module.sngtime*1000/1024) * time.Millisecond
}
// Speed returns the song speed.
func (m *Module) Speed() int { return int(m.module.sngspd) }
// Tempo returns the song tempo.
func (m *Module) Tempo() int { return int(m.module.bpm) }
// SetLoop controls whether the module's playback should loop.
func (m *Module) SetLoop(value bool) { m.module.loop = mikmodBool(value) }
// Loop returns true if the module's playback should loop, and false
// otherwise.
func (m *Module) Loop() bool { return goBool(m.module.loop) }
// SetFadeout controls whether the module's playback should fade out
// on the last pattern.
func (m *Module) SetFadeout(value bool) { m.module.fadeout = mikmodBool(value) }
// Fadeout returns true if the module's playback should fade out on
// the last pattern, and false otherwise.
func (m *Module) Fadeout() bool { return goBool(m.module.fadeout) }
// Close frees the module, making it unusable.
func (m *Module) Close() error {
C.Player_Free(m.module)
m.module = nil
return nil
}
var (
finish chan struct{}
done sync.WaitGroup
)
// updateLoop calls MikMod's update routine every 10ms. It terminates
// when the finish channel is closed, calling Done on the done
// waitgroup.
func updateLoop() {
for {
select {
case <-time.After(10 * time.Millisecond):
C.MikMod_Update()
case <-finish:
done.Done()
return
}
}
}
// Play starts playing a module.
func Play(m *Module) {
if finish != nil {
Stop()
}
C.Player_Start(m.module)
finish = make(chan struct{})
done.Add(1)
go updateLoop()
}
// Stop stops playing a module.
func Stop() {
if finish == nil {
return
}
C.Player_Stop()
close(finish)
done.Wait()
finish = nil
}
// IsPlaying returns true if the player is active, and false
// otherwise.
func IsPlaying() bool {
return int(C.Player_Active()) != 0
}
// mikmodString converts a Go string to a MikMod string; make sure to
// call C.free() on it when done with it.
func mikmodString(s string) *C.CHAR {
return (*C.CHAR)(C.CString(s))
}
// mikmodErrro returns a Go error corresponding to the current MikMod
// error.
func mikmodError() error {
return errors.New(C.GoString(C.MikMod_strerror(C.MikMod_errno)))
}
// mikmodBool converts a Go boolean to a MikMod boolean, which is
// represented as an int.
func mikmodBool(b bool) C.BOOL {
if b {
return 1
} else {
return 0
}
}
// goBool converts a MikMod boolean to a Go boolean.
func goBool(b C.BOOL) bool {
if b != 0 {
return true
} else {
return false
}
}