-
Notifications
You must be signed in to change notification settings - Fork 0
/
sequencer.go
87 lines (71 loc) · 1.76 KB
/
sequencer.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
package balafon
import (
"cmp"
"fmt"
"slices"
"strings"
"github.com/mgnsk/balafon/internal/constants"
"gitlab.com/gomidi/midi/v2/smf"
)
// SMF is an SMF song.
type SMF []TrackEvent
func (song SMF) String() string {
var s strings.Builder
for _, ev := range song {
s.WriteString(ev.String())
s.WriteString("\n")
}
return s.String()
}
// TrackEvent is an SMF track event.
type TrackEvent struct {
Message smf.Message
AbsTicks uint32
AbsNanoseconds int64
}
func (s *TrackEvent) String() string {
return fmt.Sprintf("pos: %d ns: %d message: %s", s.AbsTicks, s.AbsNanoseconds, s.Message.String())
}
// Sequencer is a MIDI sequencer.
type Sequencer struct {
song SMF
pos uint32
tempo float64
absNanoseconds int64
}
// AddBars adds bars to te sequence.
func (s *Sequencer) AddBars(bars ...*Bar) {
for _, bar := range bars {
for _, ev := range bar.Events {
te := TrackEvent{
Message: ev.Message,
AbsTicks: s.pos + ev.Pos,
AbsNanoseconds: s.absNanoseconds + constants.TicksPerQuarter.Duration(s.tempo, ev.Pos).Nanoseconds(),
}
s.song = append(s.song, te)
var newTempo float64
if ev.Message.GetMetaTempo(&newTempo) {
s.tempo = newTempo
}
}
ticks := bar.Cap()
s.pos += ticks
s.absNanoseconds += constants.TicksPerQuarter.Duration(s.tempo, ticks).Nanoseconds()
}
slices.SortStableFunc(s.song, func(a, b TrackEvent) int {
return cmp.Compare(a.AbsTicks, b.AbsTicks)
})
}
// Flush emits the accumulated SMF tracks.
func (s *Sequencer) Flush() SMF {
song := make(SMF, len(s.song))
copy(song, s.song)
s.song = s.song[:0]
return song
}
// NewSequencer creates an SMF sequencer.
func NewSequencer() *Sequencer {
return &Sequencer{
tempo: constants.DefaultTempo,
}
}