mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
refactor(tracker): put all recording data into struct Recording
This commit is contained in:
parent
453f45c48a
commit
d342fb860b
@ -20,8 +20,8 @@ import (
|
|||||||
type NullContext struct {
|
type NullContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (NullContext) NextEvent() (event tracker.PlayerProcessEvent, ok bool) {
|
func (NullContext) NextEvent() (event tracker.MIDINoteEvent, ok bool) {
|
||||||
return tracker.PlayerProcessEvent{}, false
|
return tracker.MIDINoteEvent{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (NullContext) BPM() (bpm float64, ok bool) {
|
func (NullContext) BPM() (bpm float64, ok bool) {
|
||||||
|
@ -20,7 +20,7 @@ type VSTIProcessContext struct {
|
|||||||
host vst2.Host
|
host vst2.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VSTIProcessContext) NextEvent() (event tracker.PlayerProcessEvent, ok bool) {
|
func (c *VSTIProcessContext) NextEvent() (event tracker.MIDINoteEvent, ok bool) {
|
||||||
var ev vst2.MIDIEvent
|
var ev vst2.MIDIEvent
|
||||||
for len(c.events) > 0 {
|
for len(c.events) > 0 {
|
||||||
ev, c.events = c.events[0], c.events[1:]
|
ev, c.events = c.events[0], c.events[1:]
|
||||||
@ -28,16 +28,16 @@ func (c *VSTIProcessContext) NextEvent() (event tracker.PlayerProcessEvent, ok b
|
|||||||
case ev.Data[0] >= 0x80 && ev.Data[0] < 0x90:
|
case ev.Data[0] >= 0x80 && ev.Data[0] < 0x90:
|
||||||
channel := ev.Data[0] - 0x80
|
channel := ev.Data[0] - 0x80
|
||||||
note := ev.Data[1]
|
note := ev.Data[1]
|
||||||
return tracker.PlayerProcessEvent{Frame: int(ev.DeltaFrames), On: false, Channel: int(channel), Note: note}, true
|
return tracker.MIDINoteEvent{Frame: int(ev.DeltaFrames), On: false, Channel: int(channel), Note: note}, true
|
||||||
case ev.Data[0] >= 0x90 && ev.Data[0] < 0xA0:
|
case ev.Data[0] >= 0x90 && ev.Data[0] < 0xA0:
|
||||||
channel := ev.Data[0] - 0x90
|
channel := ev.Data[0] - 0x90
|
||||||
note := ev.Data[1]
|
note := ev.Data[1]
|
||||||
return tracker.PlayerProcessEvent{Frame: int(ev.DeltaFrames), On: true, Channel: int(channel), Note: note}, true
|
return tracker.MIDINoteEvent{Frame: int(ev.DeltaFrames), On: true, Channel: int(channel), Note: note}, true
|
||||||
default:
|
default:
|
||||||
// ignore all other MIDI messages
|
// ignore all other MIDI messages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tracker.PlayerProcessEvent{}, false
|
return tracker.MIDINoteEvent{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VSTIProcessContext) BPM() (bpm float64, ok bool) {
|
func (c *VSTIProcessContext) BPM() (bpm float64, ok bool) {
|
||||||
|
@ -240,11 +240,14 @@ func (m *Model) ProcessPlayerMessage(msg PlayerMessage) {
|
|||||||
switch e := msg.Inner.(type) {
|
switch e := msg.Inner.(type) {
|
||||||
case PlayerCrashMessage:
|
case PlayerCrashMessage:
|
||||||
m.d.Panic = true
|
m.d.Panic = true
|
||||||
case PlayerRecordedMessage:
|
case Recording:
|
||||||
if e.BPM == 0 {
|
if e.BPM == 0 {
|
||||||
e.BPM = float64(m.d.Song.BPM)
|
e.BPM = float64(m.d.Song.BPM)
|
||||||
}
|
}
|
||||||
song := RecordingToSong(m.d.Song.Patch, m.d.Song.RowsPerBeat, m.d.Song.Score.RowsPerPattern, e)
|
song, err := e.Song(m.d.Song.Patch, m.d.Song.RowsPerBeat, m.d.Song.Score.RowsPerPattern)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
m.SetSong(song)
|
m.SetSong(song)
|
||||||
m.d.InstrEnlarged = false
|
m.d.InstrEnlarged = false
|
||||||
default:
|
default:
|
||||||
|
@ -25,10 +25,8 @@ type (
|
|||||||
peakVolumeMeter VolumeAnalyzer
|
peakVolumeMeter VolumeAnalyzer
|
||||||
voiceStates [vm.MAX_VOICES]float32
|
voiceStates [vm.MAX_VOICES]float32
|
||||||
|
|
||||||
recording bool
|
recState recState
|
||||||
recordingNoteArrived bool
|
recording Recording
|
||||||
recordingFrames int
|
|
||||||
recordingEvents []PlayerProcessEvent
|
|
||||||
|
|
||||||
synther sointu.Synther
|
synther sointu.Synther
|
||||||
playerMessages chan<- PlayerMessage
|
playerMessages chan<- PlayerMessage
|
||||||
@ -36,11 +34,11 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlayerProcessContext interface {
|
PlayerProcessContext interface {
|
||||||
NextEvent() (event PlayerProcessEvent, ok bool)
|
NextEvent() (event MIDINoteEvent, ok bool)
|
||||||
BPM() (bpm float64, ok bool)
|
BPM() (bpm float64, ok bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerProcessEvent struct {
|
MIDINoteEvent struct {
|
||||||
Frame int
|
Frame int
|
||||||
On bool
|
On bool
|
||||||
Channel int
|
Channel int
|
||||||
@ -51,12 +49,6 @@ type (
|
|||||||
bool
|
bool
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerRecordedMessage struct {
|
|
||||||
BPM float64 // vsts allow bpms as floats so for accurate reconstruction, keep it as float for recording
|
|
||||||
Events []PlayerProcessEvent
|
|
||||||
TotalFrames int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume and SongRow are transmitted so frequently that they are treated specially, to avoid boxing. All the
|
// Volume and SongRow are transmitted so frequently that they are treated specially, to avoid boxing. All the
|
||||||
// rest messages can be boxed to interface{}
|
// rest messages can be boxed to interface{}
|
||||||
PlayerMessage struct {
|
PlayerMessage struct {
|
||||||
@ -74,6 +66,10 @@ type (
|
|||||||
PlayerVolumeErrorMessage struct {
|
PlayerVolumeErrorMessage struct {
|
||||||
error
|
error
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
recState int
|
||||||
|
|
||||||
voiceNote struct {
|
voiceNote struct {
|
||||||
voice int
|
voice int
|
||||||
@ -85,6 +81,12 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
recStateNone recState = iota
|
||||||
|
recStateWaitingForNote
|
||||||
|
recStateRecording
|
||||||
|
)
|
||||||
|
|
||||||
const NUM_RENDER_TRIES = 10000
|
const NUM_RENDER_TRIES = 10000
|
||||||
|
|
||||||
func NewPlayer(synther sointu.Synther, playerMessages chan<- PlayerMessage, modelMessages <-chan interface{}) *Player {
|
func NewPlayer(synther sointu.Synther, playerMessages chan<- PlayerMessage, modelMessages <-chan interface{}) *Player {
|
||||||
@ -103,22 +105,22 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
|
|||||||
midi, midiOk := context.NextEvent()
|
midi, midiOk := context.NextEvent()
|
||||||
frame := 0
|
frame := 0
|
||||||
|
|
||||||
if p.recording && p.recordingNoteArrived {
|
if p.recState == recStateRecording {
|
||||||
p.recordingFrames += len(buffer)
|
p.recording.TotalFrames += len(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldBuffer := buffer
|
oldBuffer := buffer
|
||||||
|
|
||||||
for i := 0; i < NUM_RENDER_TRIES; i++ {
|
for i := 0; i < NUM_RENDER_TRIES; i++ {
|
||||||
for midiOk && frame >= midi.Frame {
|
for midiOk && frame >= midi.Frame {
|
||||||
if p.recording {
|
if p.recState == recStateWaitingForNote {
|
||||||
if !p.recordingNoteArrived {
|
p.recording.TotalFrames = len(buffer)
|
||||||
p.recordingFrames = len(buffer)
|
p.recState = recStateRecording
|
||||||
p.recordingNoteArrived = true
|
|
||||||
}
|
}
|
||||||
|
if p.recState == recStateRecording {
|
||||||
midiTotalFrame := midi
|
midiTotalFrame := midi
|
||||||
midiTotalFrame.Frame = p.recordingFrames - len(buffer)
|
midiTotalFrame.Frame = p.recording.TotalFrames - len(buffer)
|
||||||
p.recordingEvents = append(p.recordingEvents, midiTotalFrame)
|
p.recording.Events = append(p.recording.Events, midiTotalFrame)
|
||||||
}
|
}
|
||||||
if midi.On {
|
if midi.On {
|
||||||
p.triggerInstrument(midi.Channel, midi.Note)
|
p.triggerInstrument(midi.Channel, midi.Note)
|
||||||
@ -278,20 +280,14 @@ loop:
|
|||||||
}
|
}
|
||||||
case ModelRecordingMessage:
|
case ModelRecordingMessage:
|
||||||
if m.bool {
|
if m.bool {
|
||||||
p.recording = true
|
p.recState = recStateWaitingForNote
|
||||||
p.recordingEvents = make([]PlayerProcessEvent, 0)
|
p.recording = Recording{}
|
||||||
p.recordingFrames = 0
|
|
||||||
p.recordingNoteArrived = false
|
|
||||||
} else {
|
} else {
|
||||||
if p.recording && len(p.recordingEvents) > 0 {
|
if p.recState == recStateRecording && len(p.recording.Events) > 0 {
|
||||||
bpm, _ := context.BPM()
|
p.recording.BPM, _ = context.BPM()
|
||||||
p.trySend(PlayerRecordedMessage{
|
p.trySend(p.recording)
|
||||||
BPM: bpm,
|
|
||||||
Events: p.recordingEvents,
|
|
||||||
TotalFrames: p.recordingFrames,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
p.recording = false
|
p.recState = recStateNone
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// ignore unknown messages
|
// ignore unknown messages
|
||||||
|
@ -1,20 +1,31 @@
|
|||||||
package tracker
|
package tracker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type Recording struct {
|
||||||
recordingNote struct {
|
BPM float64 // vsts allow bpms as floats so for accurate reconstruction, keep it as float for recording
|
||||||
|
Events []MIDINoteEvent
|
||||||
|
TotalFrames int
|
||||||
|
}
|
||||||
|
|
||||||
|
type recordingNote struct {
|
||||||
note byte
|
note byte
|
||||||
startRow int
|
startRow int
|
||||||
endRow int
|
endRow int
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
func RecordingToSong(patch sointu.Patch, rowsPerBeat, rowsPerPattern int, recording PlayerRecordedMessage) sointu.Song {
|
var ErrInvalidRows = errors.New("rows per beat and rows per pattern must be greater than 1")
|
||||||
|
|
||||||
|
func (recording *Recording) Song(patch sointu.Patch, rowsPerBeat, rowsPerPattern int) (sointu.Song, error) {
|
||||||
|
if rowsPerBeat <= 1 || rowsPerPattern <= 1 {
|
||||||
|
return sointu.Song{}, ErrInvalidRows
|
||||||
|
}
|
||||||
channelNotes := make([][]recordingNote, 0)
|
channelNotes := make([][]recordingNote, 0)
|
||||||
// find the length of each note and assign it to its respective channel
|
// find the length of each note and assign it to its respective channel
|
||||||
for i, m := range recording.Events {
|
for i, m := range recording.Events {
|
||||||
@ -109,7 +120,7 @@ func RecordingToSong(patch sointu.Patch, rowsPerBeat, rowsPerPattern int, record
|
|||||||
continue L
|
continue L
|
||||||
}
|
}
|
||||||
for l, p2 := range patterns {
|
for l, p2 := range patterns {
|
||||||
if testEq(p, p2) {
|
if bytes.Equal(p, p2) {
|
||||||
order[k] = l
|
order[k] = l
|
||||||
continue L
|
continue L
|
||||||
}
|
}
|
||||||
@ -125,21 +136,9 @@ func RecordingToSong(patch sointu.Patch, rowsPerBeat, rowsPerPattern int, record
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
score := sointu.Score{Length: songLengthPatterns, RowsPerPattern: rowsPerPattern, Tracks: songTracks}
|
score := sointu.Score{Length: songLengthPatterns, RowsPerPattern: rowsPerPattern, Tracks: songTracks}
|
||||||
return sointu.Song{BPM: int(recording.BPM + 0.5), RowsPerBeat: rowsPerBeat, Score: score, Patch: patch.Copy()}
|
return sointu.Song{BPM: int(recording.BPM + 0.5), RowsPerBeat: rowsPerBeat, Score: score, Patch: patch.Copy()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func frameToRow(BPM float64, rowsPerBeat, frame int) int {
|
func frameToRow(BPM float64, rowsPerBeat, frame int) int {
|
||||||
return int(float64(frame)/44100/60*BPM*float64(rowsPerBeat) + 0.5)
|
return int(float64(frame)/44100/60*BPM*float64(rowsPerBeat) + 0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEq(a, b []byte) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range a {
|
|
||||||
if a[i] != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user