mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 13:48:02 -04:00
improved tracker behaviour when rewinding
better visuals for tracker window sketched in idea for a tracker "replay" feature that will allow a selection of audio output to be replayed. it works in principal but is not currently used
This commit is contained in:
parent
04c21937b9
commit
aa4da3d979
|
@ -437,7 +437,7 @@ func NewDebugger(opts CommandLineOptions, create CreateUserInterface) (*Debugger
|
|||
// playmode)
|
||||
|
||||
// add audio tracker
|
||||
dbg.Tracker = tracker.NewTracker(dbg)
|
||||
dbg.Tracker = tracker.NewTracker(dbg, dbg.Rewind)
|
||||
dbg.vcs.TIA.Audio.SetTracker(dbg.Tracker)
|
||||
|
||||
// add plug monitor
|
||||
|
|
|
@ -137,10 +137,13 @@ type imguiColors struct {
|
|||
AudioOscLine imgui.Vec4
|
||||
|
||||
// audio tracker
|
||||
AudioTrackerHeader imgui.Vec4
|
||||
AudioTrackerRow imgui.Vec4
|
||||
AudioTrackerRowAlt imgui.Vec4
|
||||
AudioTrackerRowHover imgui.Vec4
|
||||
AudioTrackerHeader imgui.Vec4
|
||||
AudioTrackerRow imgui.Vec4
|
||||
AudioTrackerRowAlt imgui.Vec4
|
||||
AudioTrackerRowSelected imgui.Vec4
|
||||
AudioTrackerRowSelectedAlt imgui.Vec4
|
||||
AudioTrackerRowHover imgui.Vec4
|
||||
AudioTrackerBorder imgui.Vec4
|
||||
|
||||
// piano keys
|
||||
PianoKeysBackground imgui.Vec4
|
||||
|
@ -330,10 +333,13 @@ func newColors() *imguiColors {
|
|||
AudioOscLine: imgui.Vec4{0.10, 0.97, 0.29, 1.0},
|
||||
|
||||
// audio tracker
|
||||
AudioTrackerHeader: imgui.Vec4{0.09, 0.09, 0.09, 1.0},
|
||||
AudioTrackerRow: imgui.Vec4{0.10, 0.10, 0.10, 1.0},
|
||||
AudioTrackerRowAlt: imgui.Vec4{0.12, 0.12, 0.12, 1.0},
|
||||
AudioTrackerRowHover: imgui.Vec4{0.12, 0.12, 0.15, 1.0},
|
||||
AudioTrackerHeader: imgui.Vec4{0.12, 0.25, 0.25, 1.0},
|
||||
AudioTrackerRow: imgui.Vec4{0.10, 0.15, 0.15, 1.0},
|
||||
AudioTrackerRowAlt: imgui.Vec4{0.12, 0.17, 0.17, 1.0},
|
||||
AudioTrackerRowSelected: imgui.Vec4{0.15, 0.25, 0.25, 1.0},
|
||||
AudioTrackerRowSelectedAlt: imgui.Vec4{0.17, 0.27, 0.27, 1.0},
|
||||
AudioTrackerRowHover: imgui.Vec4{0.14, 0.20, 0.20, 1.0},
|
||||
AudioTrackerBorder: imgui.Vec4{0.12, 0.25, 0.25, 1.0},
|
||||
|
||||
// piano keys
|
||||
PianoKeysBackground: imgui.Vec4{0.10, 0.09, 0.05, 1.0},
|
||||
|
|
52
gui/sdlimgui/imgui_selection.go
Normal file
52
gui/sdlimgui/imgui_selection.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package sdlimgui
|
||||
|
||||
type imguiSelection struct {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
|
||||
func (sel *imguiSelection) clear() {
|
||||
sel.a = -1
|
||||
sel.b = -1
|
||||
}
|
||||
|
||||
func (sel *imguiSelection) dragStart(i int) {
|
||||
sel.a = i
|
||||
sel.b = i
|
||||
}
|
||||
func (sel *imguiSelection) drag(i int) {
|
||||
sel.b = i
|
||||
}
|
||||
|
||||
func (sel imguiSelection) isSingle() bool {
|
||||
return sel.a == sel.b
|
||||
}
|
||||
|
||||
func (sel imguiSelection) inRange(i int) bool {
|
||||
if sel.b < sel.a {
|
||||
return i >= sel.b && i <= sel.a
|
||||
}
|
||||
return i >= sel.a && i <= sel.b
|
||||
}
|
||||
|
||||
func (sel imguiSelection) limits() (int, int) {
|
||||
if sel.a < sel.b {
|
||||
return sel.a, sel.b
|
||||
}
|
||||
return sel.b, sel.a
|
||||
}
|
|
@ -40,6 +40,8 @@ type winTracker struct {
|
|||
whiteKeys imgui.PackedColor
|
||||
whiteKeysGap imgui.PackedColor
|
||||
pianoKeysHeight float32
|
||||
|
||||
selection imguiSelection
|
||||
}
|
||||
|
||||
func newWinTracker(img *SdlImgui) (window, error) {
|
||||
|
@ -49,6 +51,7 @@ func newWinTracker(img *SdlImgui) (window, error) {
|
|||
whiteKeys: imgui.PackedColorFromVec4(imgui.Vec4{1.0, 1.0, 0.90, 1.0}),
|
||||
whiteKeysGap: imgui.PackedColorFromVec4(imgui.Vec4{0.2, 0.2, 0.2, 1.0}),
|
||||
}
|
||||
win.selection.clear()
|
||||
return win, nil
|
||||
}
|
||||
|
||||
|
@ -99,58 +102,44 @@ func (win *winTracker) debuggerDraw() {
|
|||
}
|
||||
|
||||
func (win *winTracker) draw() {
|
||||
imgui.PushStyleColor(imgui.StyleColorHeaderHovered, win.img.cols.DisasmHover)
|
||||
imgui.PushStyleColor(imgui.StyleColorHeaderActive, win.img.cols.DisasmHover)
|
||||
defer imgui.PopStyleColorV(2)
|
||||
|
||||
imgui.PushStyleColor(imgui.StyleColorTableHeaderBg, win.img.cols.AudioTrackerHeader)
|
||||
defer imgui.PopStyleColor()
|
||||
|
||||
tableFlags := imgui.TableFlagsNone
|
||||
tableFlags |= imgui.TableFlagsSizingFixedFit
|
||||
tableFlags |= imgui.TableFlagsBordersV
|
||||
tableFlags |= imgui.TableFlagsBordersOuter
|
||||
|
||||
const tableColumns = 14
|
||||
|
||||
tableSetupColumns := func() {
|
||||
imgui.TableSetupColumnV("", imgui.TableColumnFlagsNone, 0, 0)
|
||||
imgui.TableSetupColumnV("", imgui.TableColumnFlagsNone, 15, 1)
|
||||
imgui.TableSetupColumnV("AUDC0", imgui.TableColumnFlagsNone, 40, 2)
|
||||
imgui.TableSetupColumnV("Description", imgui.TableColumnFlagsNone, 80, 2)
|
||||
imgui.TableSetupColumnV("AUDF0", imgui.TableColumnFlagsNone, 40, 3)
|
||||
imgui.TableSetupColumnV("Note", imgui.TableColumnFlagsNone, 30, 3)
|
||||
imgui.TableSetupColumnV("AUDV0", imgui.TableColumnFlagsNone, 40, 4)
|
||||
imgui.TableSetupColumnV("", imgui.TableColumnFlagsNone, 0, 5)
|
||||
imgui.TableSetupColumnV("", imgui.TableColumnFlagsNone, 15, 6)
|
||||
imgui.TableSetupColumnV("AUDC1", imgui.TableColumnFlagsNone, 40, 2)
|
||||
imgui.TableSetupColumnV("Description", imgui.TableColumnFlagsNone, 80, 2)
|
||||
imgui.TableSetupColumnV("AUDF1", imgui.TableColumnFlagsNone, 40, 8)
|
||||
imgui.TableSetupColumnV("Note", imgui.TableColumnFlagsNone, 30, 3)
|
||||
imgui.TableSetupColumnV("AUDV1", imgui.TableColumnFlagsNone, 40, 9)
|
||||
}
|
||||
|
||||
// I can't get the header of the table to freeze in the scroller so I'm
|
||||
// fudging the effect by having a separate table just for the header.
|
||||
if !imgui.BeginTableV("trackerHeader", tableColumns, tableFlags, imgui.Vec2{}, 0) {
|
||||
return
|
||||
}
|
||||
tableSetupColumns()
|
||||
imgui.TableHeadersRow()
|
||||
imgui.EndTable()
|
||||
imgui.PushStyleColor(imgui.StyleColorTableBorderLight, win.img.cols.AudioTrackerBorder)
|
||||
imgui.PushStyleColor(imgui.StyleColorTableBorderStrong, win.img.cols.AudioTrackerBorder)
|
||||
imgui.PushStyleColor(imgui.StyleColorHeaderHovered, win.img.cols.AudioTrackerRowHover)
|
||||
imgui.PushStyleColor(imgui.StyleColorHeaderActive, win.img.cols.AudioTrackerRowHover)
|
||||
defer imgui.PopStyleColorV(5)
|
||||
|
||||
// new child that contains the main scrollable table
|
||||
if imgui.BeginChildV("##trackerscroller", imgui.Vec2{X: 0, Y: imguiRemainingWinHeight() - win.pianoKeysHeight}, false, 0) {
|
||||
numEntries := len(win.img.lz.Tracker.Entries)
|
||||
if numEntries == 0 {
|
||||
imgui.Spacing()
|
||||
imgui.Text("No audio output/changes yet")
|
||||
imgui.Text("Audio tracker history is empty")
|
||||
} else {
|
||||
if imgui.BeginTableV("tracker", tableColumns, tableFlags, imgui.Vec2{}, 0) {
|
||||
tableSetupColumns()
|
||||
// tracker table
|
||||
const numColumns = 12
|
||||
flgs := imgui.TableFlagsScrollY
|
||||
flgs |= imgui.TableFlagsSizingStretchProp
|
||||
flgs |= imgui.TableFlagsNoHostExtendX
|
||||
|
||||
if imgui.BeginTableV("tracker", numColumns, flgs, imgui.Vec2{}, 0) {
|
||||
imgui.TableSetupColumnV("", imgui.TableColumnFlagsNone, 15, 0)
|
||||
imgui.TableSetupColumnV("AUDC0", imgui.TableColumnFlagsNone, 40, 1)
|
||||
imgui.TableSetupColumnV("Distortion", imgui.TableColumnFlagsNone, 80, 2)
|
||||
imgui.TableSetupColumnV("AUDF0", imgui.TableColumnFlagsNone, 40, 3)
|
||||
imgui.TableSetupColumnV("Note", imgui.TableColumnFlagsNone, 30, 4)
|
||||
imgui.TableSetupColumnV("AUDV0", imgui.TableColumnFlagsNone, 40, 5)
|
||||
imgui.TableSetupColumnV("", imgui.TableColumnFlagsNone, 15, 6)
|
||||
imgui.TableSetupColumnV("AUDC1", imgui.TableColumnFlagsNone, 40, 7)
|
||||
imgui.TableSetupColumnV("Distortion", imgui.TableColumnFlagsNone, 80, 8)
|
||||
imgui.TableSetupColumnV("AUDF1", imgui.TableColumnFlagsNone, 40, 9)
|
||||
imgui.TableSetupColumnV("Note", imgui.TableColumnFlagsNone, 30, 10)
|
||||
imgui.TableSetupColumnV("AUDV1", imgui.TableColumnFlagsNone, 40, 11)
|
||||
|
||||
imgui.TableSetupScrollFreeze(0, 1)
|
||||
imgui.TableHeadersRow()
|
||||
|
||||
// altenate row colors at change of frame number
|
||||
var lastEntry tracker.Entry
|
||||
var lastEntryChan0 tracker.Entry
|
||||
var lastEntryChan1 tracker.Entry
|
||||
var altRowCol bool
|
||||
|
@ -162,22 +151,35 @@ func (win *winTracker) draw() {
|
|||
entry := win.img.lz.Tracker.Entries[i]
|
||||
|
||||
imgui.TableNextRow()
|
||||
|
||||
// flip row color
|
||||
if entry.Coords.Frame != lastEntry.Coords.Frame {
|
||||
altRowCol = !altRowCol
|
||||
}
|
||||
altRowCol = !altRowCol
|
||||
|
||||
if altRowCol {
|
||||
imgui.TableSetBgColor(imgui.TableBgTargetRowBg0, win.img.cols.AudioTrackerRowAlt)
|
||||
imgui.TableSetBgColor(imgui.TableBgTargetRowBg1, win.img.cols.AudioTrackerRowAlt)
|
||||
if win.selection.inRange(i) {
|
||||
imgui.TableSetBgColor(imgui.TableBgTargetRowBg0, win.img.cols.AudioTrackerRowSelectedAlt)
|
||||
imgui.TableSetBgColor(imgui.TableBgTargetRowBg1, win.img.cols.AudioTrackerRowSelectedAlt)
|
||||
}
|
||||
} else {
|
||||
imgui.TableSetBgColor(imgui.TableBgTargetRowBg0, win.img.cols.AudioTrackerRow)
|
||||
imgui.TableSetBgColor(imgui.TableBgTargetRowBg1, win.img.cols.AudioTrackerRow)
|
||||
if win.selection.inRange(i) {
|
||||
imgui.TableSetBgColor(imgui.TableBgTargetRowBg0, win.img.cols.AudioTrackerRowSelected)
|
||||
imgui.TableSetBgColor(imgui.TableBgTargetRowBg1, win.img.cols.AudioTrackerRowSelected)
|
||||
}
|
||||
}
|
||||
|
||||
// add selectable for row. the text for the selectable
|
||||
// depends on which channel the tracker entry represents
|
||||
imgui.TableNextColumn()
|
||||
imgui.SelectableV("", false, imgui.SelectableFlagsSpanAllColumns, imgui.Vec2{0, 0})
|
||||
if entry.Channel == 1 || !entry.IsMusical() {
|
||||
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.Transparent)
|
||||
}
|
||||
imgui.SelectableV(string(fonts.MusicNote), false, imgui.SelectableFlagsSpanAllColumns, imgui.Vec2{0, 0})
|
||||
if entry.Channel == 1 || !entry.IsMusical() {
|
||||
imgui.PopStyleColor()
|
||||
}
|
||||
|
||||
imguiTooltip(func() {
|
||||
imgui.Text(fmt.Sprintf("Frame: %d", entry.Coords.Frame))
|
||||
imgui.Text(fmt.Sprintf("Scanline: %d", entry.Coords.Scanline))
|
||||
|
@ -185,19 +187,33 @@ func (win *winTracker) draw() {
|
|||
}, true)
|
||||
|
||||
// context menu on right mouse button
|
||||
if imgui.IsItemHovered() && imgui.IsMouseDown(1) {
|
||||
imgui.OpenPopup(trackerContextMenuID)
|
||||
win.contextMenu = entry.Coords
|
||||
if imgui.IsItemHovered() {
|
||||
if imgui.IsMouseClicked(0) {
|
||||
win.selection.dragStart(i)
|
||||
}
|
||||
if imgui.IsMouseDragging(0, 0.0) {
|
||||
win.selection.drag(i)
|
||||
}
|
||||
if imgui.IsMouseDown(1) {
|
||||
imgui.OpenPopup(trackerContextMenuID)
|
||||
win.contextMenu = entry.Coords
|
||||
}
|
||||
}
|
||||
if entry.Coords == win.contextMenu {
|
||||
if imgui.BeginPopup(trackerContextMenuID) {
|
||||
if imgui.Selectable("Rewind to") {
|
||||
if imgui.Selectable("Clear note history") {
|
||||
win.img.dbg.PushFunction(win.img.dbg.Tracker.Reset)
|
||||
}
|
||||
if imgui.Selectable(fmt.Sprintf("Rewind (to %s)", entry.Coords)) {
|
||||
win.img.dbg.GotoCoords(entry.Coords)
|
||||
}
|
||||
imgui.EndPopup()
|
||||
}
|
||||
}
|
||||
|
||||
// if tracker entry is for channel one then skip the
|
||||
// first half-dozen columns and add whatever the 'note
|
||||
// icon' is
|
||||
if entry.Channel == 1 {
|
||||
imgui.TableNextColumn()
|
||||
imgui.TableNextColumn()
|
||||
|
@ -205,21 +221,9 @@ func (win *winTracker) draw() {
|
|||
imgui.TableNextColumn()
|
||||
imgui.TableNextColumn()
|
||||
imgui.TableNextColumn()
|
||||
imgui.TableNextColumn()
|
||||
}
|
||||
|
||||
// convert musical note into something worth showing
|
||||
musicalNote := string(entry.MusicalNote)
|
||||
imgui.TableNextColumn()
|
||||
switch entry.MusicalNote {
|
||||
case tracker.Noise:
|
||||
musicalNote = ""
|
||||
case tracker.Low:
|
||||
musicalNote = ""
|
||||
case tracker.Silence:
|
||||
musicalNote = ""
|
||||
default:
|
||||
imgui.Text(fmt.Sprintf("%c", fonts.MusicNote))
|
||||
if entry.IsMusical() {
|
||||
imgui.Text(string(fonts.MusicNote))
|
||||
}
|
||||
}
|
||||
|
||||
imgui.TableNextColumn()
|
||||
|
@ -229,7 +233,20 @@ func (win *winTracker) draw() {
|
|||
imgui.TableNextColumn()
|
||||
imgui.Text(fmt.Sprintf("%05b", entry.Registers.Freq&0x1f))
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text(musicalNote)
|
||||
|
||||
// convert musical note into something worth showing
|
||||
musicalNote := string(entry.MusicalNote)
|
||||
switch entry.MusicalNote {
|
||||
case tracker.Noise:
|
||||
musicalNote = ""
|
||||
case tracker.Low:
|
||||
musicalNote = ""
|
||||
case tracker.Silence:
|
||||
musicalNote = ""
|
||||
default:
|
||||
imgui.Text(musicalNote)
|
||||
}
|
||||
|
||||
imgui.TableNextColumn()
|
||||
|
||||
// volum column
|
||||
|
@ -253,21 +270,39 @@ func (win *winTracker) draw() {
|
|||
}
|
||||
|
||||
imgui.Text(fmt.Sprintf("%02d %c", entry.Registers.Volume&0x4b, volumeArrow))
|
||||
|
||||
// record last entry for comparison purposes next iteration
|
||||
lastEntry = entry
|
||||
}
|
||||
}
|
||||
|
||||
imgui.EndTable()
|
||||
}
|
||||
if win.img.dbg.State() == govern.Running {
|
||||
imgui.SetScrollHereY(1.0)
|
||||
}
|
||||
|
||||
if win.img.dbg.State() == govern.Running {
|
||||
imgui.SetScrollHereY(1.0)
|
||||
imgui.EndTable()
|
||||
}
|
||||
}
|
||||
}
|
||||
imgui.EndChild()
|
||||
|
||||
win.pianoKeysHeight = win.drawPianoKeys()
|
||||
// don't allow grabbing or movement of window when piano keys are clicked
|
||||
if imgui.BeginChildV("##pianokeys", imgui.Vec2{}, false, imgui.WindowFlagsNoMove) {
|
||||
win.pianoKeysHeight = win.drawPianoKeys()
|
||||
}
|
||||
imgui.EndChild()
|
||||
}
|
||||
|
||||
// *** replay button not currently used because the replay mechanism is currently unsatisfactory ***
|
||||
func (win *winTracker) drawReplayButton() {
|
||||
// disable replay button as appropriate
|
||||
s, e := win.selection.limits()
|
||||
if s == -1 || e == -1 || win.img.dbg.State() != govern.Paused {
|
||||
imgui.PushItemFlag(imgui.ItemFlagsDisabled, true)
|
||||
imgui.PushStyleVarFloat(imgui.StyleVarAlpha, disabledAlpha)
|
||||
defer imgui.PopItemFlag()
|
||||
defer imgui.PopStyleVar()
|
||||
}
|
||||
|
||||
if imgui.Button("Replay") {
|
||||
// *** this should be run asynchronously ***
|
||||
win.img.dbg.Tracker.Replay(s, e, win.img.audio)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ import (
|
|||
// Tracker implementations display or otherwise record the state of the audio
|
||||
// registers for each channel.
|
||||
type Tracker interface {
|
||||
// Tick is called every video cycle
|
||||
Tick(channel int, reg Registers)
|
||||
// AudioTick is called every video cycle
|
||||
AudioTick(channel int, reg Registers)
|
||||
}
|
||||
|
||||
// SampleFreq represents the number of samples generated per second. This is
|
||||
|
@ -101,11 +101,11 @@ func (au *Audio) Step() bool {
|
|||
if au.tracker != nil {
|
||||
// it's impossible for both channels to have changed in a single video cycle
|
||||
if au.channel0.registersChanged {
|
||||
au.tracker.Tick(0, au.channel0.registers)
|
||||
au.tracker.AudioTick(0, au.channel0.registers)
|
||||
au.channel0.registersChanged = false
|
||||
au.registersChanged = true
|
||||
} else if au.channel1.registersChanged {
|
||||
au.tracker.Tick(1, au.channel1.registers)
|
||||
au.tracker.AudioTick(1, au.channel1.registers)
|
||||
au.channel1.registersChanged = false
|
||||
au.registersChanged = true
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func LookupDistortion(reg audio.Registers) string {
|
|||
// same as 4
|
||||
return "Pure"
|
||||
case 6:
|
||||
return "Puzzy"
|
||||
return "Buzzy/Pure"
|
||||
case 7:
|
||||
return "Reedy"
|
||||
case 8:
|
||||
|
@ -52,7 +52,7 @@ func LookupDistortion(reg audio.Registers) string {
|
|||
return "Reedy"
|
||||
case 10:
|
||||
// same as 6
|
||||
return "Puzzy"
|
||||
return "Buzzy/Pure"
|
||||
case 11:
|
||||
// same as 0
|
||||
return "-"
|
||||
|
|
|
@ -23,7 +23,7 @@ const NoPianoKey = 0
|
|||
|
||||
// NoteToPianoKey converts the musical note to the corresponding piano key.
|
||||
//
|
||||
// Handle sharps but not flats.
|
||||
// Black notes are negative values of the white note it is associated with.
|
||||
func NoteToPianoKey(note MusicalNote) PianoKey {
|
||||
switch note {
|
||||
case "A#0":
|
||||
|
|
73
tracker/replay.go
Normal file
73
tracker/replay.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jetsetilly/gopher2600/debugger/govern"
|
||||
"github.com/jetsetilly/gopher2600/hardware"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television"
|
||||
"github.com/jetsetilly/gopher2600/rewind"
|
||||
)
|
||||
|
||||
// create replay emulation if it has not been created already
|
||||
func (tr *Tracker) createReplayEmulation(mixer television.AudioMixer) error {
|
||||
if tr.replayEmulation != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tv, err := television.NewTelevision("AUTO")
|
||||
if err != nil {
|
||||
return fmt.Errorf("tracker: create replay emulation: %w", err)
|
||||
}
|
||||
tv.AddAudioMixer(mixer)
|
||||
|
||||
tr.replayEmulation, err = hardware.NewVCS(tv, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tracker: create replay emulation: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replay audio from start to end indexes
|
||||
func (tr *Tracker) Replay(start int, end int, mixer television.AudioMixer) error {
|
||||
// the replay will run even if the master emulation is running. this may
|
||||
// cause audible issues with the hardware audio mixing
|
||||
|
||||
tr.createReplayEmulation(mixer)
|
||||
|
||||
startState := tr.rewind.GetState(tr.entries[start].Coords.Frame)
|
||||
if startState == nil {
|
||||
return fmt.Errorf("tracker: replay: can't find rewind state for frame %d", tr.entries[start].Coords.Frame)
|
||||
}
|
||||
|
||||
rewind.Plumb(tr.replayEmulation, startState, true)
|
||||
tr.replayEmulation.DetatchEmulationExtras()
|
||||
|
||||
err := tr.replayEmulation.Run(func() (govern.State, error) {
|
||||
if tr.replayEmulation.TV.GetCoords().Frame > tr.entries[end].Coords.Frame {
|
||||
return govern.Ending, nil
|
||||
}
|
||||
return govern.Running, nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("tracker: replay: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -17,62 +17,84 @@ package tracker
|
|||
|
||||
import (
|
||||
"github.com/jetsetilly/gopher2600/debugger/govern"
|
||||
"github.com/jetsetilly/gopher2600/hardware"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television/coords"
|
||||
"github.com/jetsetilly/gopher2600/hardware/tia/audio"
|
||||
"github.com/jetsetilly/gopher2600/rewind"
|
||||
)
|
||||
|
||||
// Emulation defines as much of the emulation we require access to.
|
||||
type Rewind interface {
|
||||
GetState(frame int) *rewind.State
|
||||
}
|
||||
|
||||
type Emulation interface {
|
||||
State() govern.State
|
||||
TV() *television.Television
|
||||
}
|
||||
|
||||
// Entry represents a single change of audio for a channel
|
||||
type Entry struct {
|
||||
Coords coords.TelevisionCoords
|
||||
// the (TV) time the change occurred
|
||||
Coords coords.TelevisionCoords
|
||||
|
||||
// which channel the Registers field refers to
|
||||
Channel int
|
||||
Registers audio.Registers
|
||||
|
||||
// description of the change. the Registers field by comparison contains the
|
||||
// numeric information of the audio change
|
||||
Distortion string
|
||||
MusicalNote MusicalNote
|
||||
PianoKey PianoKey
|
||||
}
|
||||
|
||||
// IsMusical returns true if entry represents a musical note
|
||||
func (e Entry) IsMusical() bool {
|
||||
return e.MusicalNote != Noise && e.MusicalNote != Silence && e.MusicalNote != Low
|
||||
}
|
||||
|
||||
const maxTrackerEntries = 1024
|
||||
|
||||
// Tracker implements the audio.Tracker interface and keeps a history of the
|
||||
// audio registers over time.
|
||||
// audio registers over time
|
||||
type Tracker struct {
|
||||
emulation Emulation
|
||||
rewind Rewind
|
||||
|
||||
// list of tracker entries. length is capped to maxTrackerEntries
|
||||
entries []Entry
|
||||
|
||||
// previous register values so we can compare to see whether the registers
|
||||
// have change and thus worth recording
|
||||
prevRegister [2]audio.Registers
|
||||
|
||||
// the most recent information for each channel. the entries do no need to
|
||||
// have happened at the same time. ie. lastEntry[0] might refer to an audio
|
||||
// change on frame 10 and lastEntry[1] on frame 20
|
||||
lastEntry [2]Entry
|
||||
|
||||
// emulation used for replaying tracker entries. it wil be created on demand
|
||||
// on the first call to Replay()
|
||||
replayEmulation *hardware.VCS
|
||||
}
|
||||
|
||||
const maxTrackerEntries = 1024
|
||||
|
||||
// NewTracker is the preferred method of initialisation for the Tracker type.
|
||||
func NewTracker(emulation Emulation) *Tracker {
|
||||
// NewTracker is the preferred method of initialisation for the Tracker type
|
||||
func NewTracker(emulation Emulation, rewind Rewind) *Tracker {
|
||||
return &Tracker{
|
||||
emulation: emulation,
|
||||
rewind: rewind,
|
||||
entries: make([]Entry, 0, maxTrackerEntries),
|
||||
}
|
||||
}
|
||||
|
||||
// Reset removes all entries from tracker list.
|
||||
// Reset removes all entries from tracker list
|
||||
func (tr *Tracker) Reset() {
|
||||
tr.entries = tr.entries[:0]
|
||||
}
|
||||
|
||||
// Tick implements the audio.Tracker interface
|
||||
func (tr *Tracker) Tick(channel int, reg audio.Registers) {
|
||||
if tr.emulation.State() == govern.Rewinding {
|
||||
return
|
||||
}
|
||||
|
||||
// AudioTick implements the audio.Tracker interface
|
||||
func (tr *Tracker) AudioTick(channel int, reg audio.Registers) {
|
||||
changed := !audio.CmpRegisters(reg, tr.prevRegister[channel])
|
||||
tr.prevRegister[channel] = reg
|
||||
|
||||
|
@ -87,23 +109,33 @@ func (tr *Tracker) Tick(channel int, reg audio.Registers) {
|
|||
MusicalNote: LookupMusicalNote(tv, reg),
|
||||
}
|
||||
e.PianoKey = NoteToPianoKey(e.MusicalNote)
|
||||
tr.entries = append(tr.entries, e)
|
||||
|
||||
if len(tr.entries) > maxTrackerEntries {
|
||||
tr.entries = tr.entries[1:]
|
||||
if tr.emulation.State() != govern.Rewinding {
|
||||
// find splice point in tracker
|
||||
splice := len(tr.entries) - 1
|
||||
for splice > 0 && !coords.GreaterThan(e.Coords, tr.entries[splice].Coords) {
|
||||
splice--
|
||||
}
|
||||
tr.entries = tr.entries[:splice+1]
|
||||
|
||||
// add new entry and limit number of entries
|
||||
tr.entries = append(tr.entries, e)
|
||||
if len(tr.entries) > maxTrackerEntries {
|
||||
tr.entries = tr.entries[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// store recent entry
|
||||
// store entry in lastEntry reference
|
||||
tr.lastEntry[channel] = e
|
||||
}
|
||||
}
|
||||
|
||||
// Copy makes a copy of the Tracker entries.
|
||||
// Copy makes a copy of the Tracker entries
|
||||
func (tr *Tracker) Copy() []Entry {
|
||||
return tr.entries
|
||||
}
|
||||
|
||||
// GetLast entry for channel.
|
||||
// GetLast entry for channel
|
||||
func (tr *Tracker) GetLast(channel int) Entry {
|
||||
return tr.lastEntry[channel]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue