tabscroll 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/tabscroll +72 -0
- data/lib/tabscroll/engine.rb +95 -0
- data/lib/tabscroll/popup.rb +60 -0
- data/lib/tabscroll/screen.rb +125 -0
- data/lib/tabscroll/timer.rb +89 -0
- data/lib/tabscroll/track.rb +203 -0
- data/lib/tabscroll.rb +40 -0
- metadata +52 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a07da9616f2254f2fd9bd14e11a5e8f327c132df
|
4
|
+
data.tar.gz: 854189ab82e34c155233f7b5b9b8ac0768105761
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 00977e49cde6a2ccf4666510585224a82fdd4efbc0d38b9e3f38008eced4f969d0d63b0b693285bea776feb6aa2589be7decd1ebba1346365c1ede6e80df4e86
|
7
|
+
data.tar.gz: d1a0f44480d736aa8f1c9a7b2f171d873d79b3d66c9a4d715b85eabee982552606c9d1aaa23cba8e37795b0136a7a11a6086f15b7be804154d5e68a437187ef6
|
data/bin/tabscroll
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# The main `tabscroll` executable
|
4
|
+
|
5
|
+
require 'tabscroll'
|
6
|
+
|
7
|
+
begin
|
8
|
+
filename = ARGV[0]
|
9
|
+
if not filename
|
10
|
+
puts "Usage:"
|
11
|
+
puts " $ #{PROGRAM_NAME} (filename)"
|
12
|
+
exit 666
|
13
|
+
end
|
14
|
+
|
15
|
+
$engine = Engine.new
|
16
|
+
if not $engine
|
17
|
+
puts 'Failed to start curses!'
|
18
|
+
exit 1337
|
19
|
+
end
|
20
|
+
$engine.timeout 10
|
21
|
+
|
22
|
+
win = Screen.new(0, 1, $engine.width, $engine.height - 2)
|
23
|
+
track = Track.new win
|
24
|
+
track.load filename
|
25
|
+
track.auto_scroll true
|
26
|
+
|
27
|
+
titlebar = Screen.new(0, 0, $engine.width, 1)
|
28
|
+
statusbar = Screen.new(0, ($engine.height - 1), $engine.width, 1)
|
29
|
+
|
30
|
+
bars_hidden = false
|
31
|
+
finished = false
|
32
|
+
while not finished
|
33
|
+
# WHY DOES GETCH CLEARS UP THE WHOLE SCREEN?
|
34
|
+
# IT DOESNT MAKE ANY SENSE
|
35
|
+
c = $engine.getchar
|
36
|
+
case c
|
37
|
+
when 'e'
|
38
|
+
track.end
|
39
|
+
when 'a'
|
40
|
+
track.begin
|
41
|
+
when 'h'
|
42
|
+
show_help_window
|
43
|
+
when 'o'
|
44
|
+
bars_hidden = (bars_hidden ? false : true)
|
45
|
+
Curses::clear
|
46
|
+
when '<'
|
47
|
+
track.scroll -5
|
48
|
+
when '>'
|
49
|
+
track.scroll 5
|
50
|
+
when Curses::KEY_LEFT
|
51
|
+
track.speed -= 1
|
52
|
+
when Curses::KEY_RIGHT
|
53
|
+
track.speed += 1
|
54
|
+
when Curses::KEY_DOWN, Curses::KEY_UP
|
55
|
+
track.speed = 0
|
56
|
+
when 'q'
|
57
|
+
quit
|
58
|
+
end
|
59
|
+
|
60
|
+
track.update
|
61
|
+
track.show
|
62
|
+
if not bars_hidden
|
63
|
+
titlebar.mvaddstr(0, 0, "#{filename} (#{track.percent_completed}%) ", Engine::Colors[:cyan])
|
64
|
+
titlebar.mvaddstr_right(0, " Speed: #{track.speed}", Engine::Colors[:cyan])
|
65
|
+
|
66
|
+
statusbar.mvaddstr_left(0, "#{PROGRAM_NAME} v#{PROGRAM_VERSION} - press `h` for help", Engine::Colors[:green])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
quit
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
require 'curses'
|
3
|
+
|
4
|
+
# The main interface with Curses.
|
5
|
+
#
|
6
|
+
# This acts as a middleman, abstracting away curses' details.
|
7
|
+
class Engine
|
8
|
+
|
9
|
+
# All possible colors.
|
10
|
+
Colors = {
|
11
|
+
:black => 1,
|
12
|
+
:white => 2,
|
13
|
+
:red => 3,
|
14
|
+
:yellow => 4,
|
15
|
+
:magenta => 5,
|
16
|
+
:blue => 6,
|
17
|
+
:green => 7,
|
18
|
+
:cyan => 8
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# Initializes Ncurses with minimal +width+ and +height+.
|
22
|
+
#
|
23
|
+
def initialize(min_width=nil, min_height=nil)
|
24
|
+
@has_colors = nil
|
25
|
+
|
26
|
+
@screen = Curses::init_screen
|
27
|
+
return nil if not @screen
|
28
|
+
|
29
|
+
if min_width and min_height
|
30
|
+
cur_width = @screen.maxx
|
31
|
+
cur_height = @screen.maxy
|
32
|
+
|
33
|
+
if cur_width < @width or cur_height < @height
|
34
|
+
self.exit
|
35
|
+
$stderr << "Error: Screen size too small (#{cur_width}x#{cur_height})\n"
|
36
|
+
$stderr << "Please resize your terminal to at least #{@width}x#{@height}\n"
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@has_colors = Curses.has_colors?
|
42
|
+
if @has_colors
|
43
|
+
Curses.start_color
|
44
|
+
Curses.use_default_colors # will use default background
|
45
|
+
|
46
|
+
# Initializes: constant foreground bg
|
47
|
+
Curses.init_pair(Colors[:white], Curses::COLOR_BLACK, -1)
|
48
|
+
Curses.init_pair(Colors[:blue], Curses::COLOR_BLUE, -1)
|
49
|
+
Curses.init_pair(Colors[:red], Curses::COLOR_RED, -1)
|
50
|
+
Curses.init_pair(Colors[:green], Curses::COLOR_GREEN, -1)
|
51
|
+
Curses.init_pair(Colors[:magenta], Curses::COLOR_MAGENTA, -1)
|
52
|
+
Curses.init_pair(Colors[:yellow], Curses::COLOR_YELLOW, -1)
|
53
|
+
Curses.init_pair(Colors[:cyan], Curses::COLOR_CYAN, -1)
|
54
|
+
end
|
55
|
+
|
56
|
+
Curses::cbreak
|
57
|
+
Curses::curs_set 0
|
58
|
+
Curses::noecho
|
59
|
+
Curses::nonl
|
60
|
+
Curses::stdscr.keypad = true # extra keys
|
61
|
+
end
|
62
|
+
|
63
|
+
def width
|
64
|
+
return Curses::cols
|
65
|
+
end
|
66
|
+
|
67
|
+
def height
|
68
|
+
return Curses::lines
|
69
|
+
end
|
70
|
+
|
71
|
+
def exit
|
72
|
+
Curses::refresh
|
73
|
+
Curses::close_screen
|
74
|
+
end
|
75
|
+
|
76
|
+
def set_color color
|
77
|
+
if @has_colors
|
78
|
+
@screen.attron Curses::color_pair(color)
|
79
|
+
return self
|
80
|
+
else
|
81
|
+
return nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def getchar
|
86
|
+
return Curses::getch
|
87
|
+
end
|
88
|
+
|
89
|
+
# +timeout+ says how many milliseconds we wait for a key to be
|
90
|
+
# pressed.
|
91
|
+
def timeout timeout
|
92
|
+
Curses::timeout = timeout
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
require_relative 'screen.rb'
|
3
|
+
|
4
|
+
# A simple centralized popup.
|
5
|
+
#
|
6
|
+
# It resizes as you place the text on it.
|
7
|
+
class Popup < Screen
|
8
|
+
|
9
|
+
def initialize(title, text)
|
10
|
+
@title = title
|
11
|
+
@text = []
|
12
|
+
text.each_line do |line|
|
13
|
+
@text += [line.chomp]
|
14
|
+
end
|
15
|
+
|
16
|
+
max_width = title.length
|
17
|
+
max_height = 1
|
18
|
+
|
19
|
+
@text.each do |line|
|
20
|
+
max_width = line.length if line.length > max_width
|
21
|
+
max_height += 1
|
22
|
+
end
|
23
|
+
|
24
|
+
max_width += 2 # left-right borders
|
25
|
+
max_height += 1 # down border
|
26
|
+
|
27
|
+
x = Curses::cols/2 - max_width/2
|
28
|
+
y = Curses::lines/2 - max_height/2
|
29
|
+
|
30
|
+
super(x, y, max_width, max_height)
|
31
|
+
self.background ' '
|
32
|
+
self.box
|
33
|
+
|
34
|
+
self.mvaddstr_center(0, title, Engine::Colors[:cyan])
|
35
|
+
|
36
|
+
y = 1
|
37
|
+
@text.each do |line|
|
38
|
+
self.mvaddstr(1, y, line)
|
39
|
+
y += 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def show
|
44
|
+
finished = false
|
45
|
+
while not finished
|
46
|
+
c = Curses::getch
|
47
|
+
case c
|
48
|
+
when 'q'
|
49
|
+
return true
|
50
|
+
when 'h'
|
51
|
+
finished = true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Curses::stdscr.clear
|
56
|
+
Curses::stdscr.refresh
|
57
|
+
return false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
|
2
|
+
require 'curses'
|
3
|
+
|
4
|
+
# A segment of the terminal screen.
|
5
|
+
#
|
6
|
+
# BUG WARNING HACK FUCK
|
7
|
+
# Whenever I use @win.attrset/@win.setpos/@win.addch
|
8
|
+
# it doesn't work at all.
|
9
|
+
# Apparently, when I do this, Curses::getch clears up
|
10
|
+
# the entire screen.
|
11
|
+
#
|
12
|
+
# DO NOT DO THIS
|
13
|
+
#
|
14
|
+
class Screen
|
15
|
+
attr_reader :width, :height
|
16
|
+
|
17
|
+
# Creates a Screen at `x` `y` `w` `h`.
|
18
|
+
def initialize(x, y, w, h)
|
19
|
+
@win = Curses::Window.new(h, w, y, x)
|
20
|
+
@width = w
|
21
|
+
@height = h
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets the current color of the Screen.
|
25
|
+
def set_color color
|
26
|
+
Curses::attrset(Curses::color_pair color)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Executes a block of code encapsulated within a color on/off.
|
30
|
+
# Note that the color can be overrided.
|
31
|
+
def with_color(color=nil)
|
32
|
+
Curses::attron(Curses::color_pair color) if color
|
33
|
+
yield
|
34
|
+
Curses::attroff(Curses::color_pair color) if color
|
35
|
+
end
|
36
|
+
|
37
|
+
# Puts a character +c+ on (+x+, +y+) with optional +color+.
|
38
|
+
def mvaddch(x, y, c, color=nil)
|
39
|
+
return if x < 0 or x >= @width
|
40
|
+
return if y < 0 or y >= @height
|
41
|
+
|
42
|
+
self.with_color color do
|
43
|
+
Curses::setpos(@win.begy + y, @win.begx + x)
|
44
|
+
Curses::addch c
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Puts a string +str+ on (+x+, +y+) with optional +color+.
|
49
|
+
def mvaddstr(x, y, str, color=nil)
|
50
|
+
return if x < 0 or x >= @width
|
51
|
+
return if y < 0 or y >= @height
|
52
|
+
|
53
|
+
self.with_color color do
|
54
|
+
# @win.setpos(@win.begy + y, @win.begx + x)
|
55
|
+
# @win.addstr str
|
56
|
+
Curses::setpos(@win.begy + y, @win.begx + x)
|
57
|
+
Curses::addstr str
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Puts a string +str+ centered on +y+ with optional +color+.
|
62
|
+
def mvaddstr_center(y, str, color=nil)
|
63
|
+
x = (@width/2) - (str.length/2)
|
64
|
+
self.mvaddstr(x, y, str, color)
|
65
|
+
end
|
66
|
+
|
67
|
+
def mvaddstr_left(y, str, color=nil)
|
68
|
+
self.mvaddstr(0, y, str, color)
|
69
|
+
end
|
70
|
+
|
71
|
+
def mvaddstr_right(y, str, color=nil)
|
72
|
+
x = @width - str.length
|
73
|
+
self.mvaddstr(x, y, str, color)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Erases all of the Screen's contents
|
77
|
+
def clear
|
78
|
+
@win.clear
|
79
|
+
end
|
80
|
+
|
81
|
+
# Commits the changes on the Screen.
|
82
|
+
def refresh
|
83
|
+
@win.refresh
|
84
|
+
end
|
85
|
+
|
86
|
+
# Moves window so that the upper-left corner is at `x` `y`.
|
87
|
+
def move(x, y)
|
88
|
+
@win.move(y, x)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Resizes window to +width+ and +h+eight.
|
92
|
+
def resize(w, h)
|
93
|
+
@win.resize(h, w)
|
94
|
+
@width = w
|
95
|
+
@height = h
|
96
|
+
end
|
97
|
+
|
98
|
+
# Set block/nonblocking reads for window.
|
99
|
+
#
|
100
|
+
# * If `delay` is negative, blocking read is used.
|
101
|
+
# * If `delay` is zero, nonblocking read is used.
|
102
|
+
# * If `delay` is positive, waits for `delay` milliseconds and
|
103
|
+
# returns ERR of no input.
|
104
|
+
def timeout(delay=-1)
|
105
|
+
@win.timeout = delay
|
106
|
+
end
|
107
|
+
|
108
|
+
def background char
|
109
|
+
@win.bkgd char
|
110
|
+
@win.refresh
|
111
|
+
end
|
112
|
+
|
113
|
+
# Sets the Screen border.
|
114
|
+
#
|
115
|
+
# * If all arguments are set, that's ok.
|
116
|
+
# * If only the first 2 arguments are set, they are the vertical
|
117
|
+
# and horizontal chars.
|
118
|
+
#
|
119
|
+
def box(horizontal=0, vertical=0)
|
120
|
+
@win.box(horizontal, vertical)
|
121
|
+
@win.refresh
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# A simple timer that counts in seconds.
|
2
|
+
#
|
3
|
+
# Usage:
|
4
|
+
# timer = Timer.new
|
5
|
+
# timer.start
|
6
|
+
# ...
|
7
|
+
# if timer.delta > 0.5 # half a second
|
8
|
+
# ...
|
9
|
+
#
|
10
|
+
class Timer
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@is_running = false
|
14
|
+
@is_paused = false
|
15
|
+
end
|
16
|
+
|
17
|
+
# Starts counting.
|
18
|
+
def start
|
19
|
+
return if @is_running
|
20
|
+
|
21
|
+
@start_time = Time.now
|
22
|
+
@stop_time = 0.0
|
23
|
+
@paused_time = 0.0
|
24
|
+
@is_running = true
|
25
|
+
@is_paused = false
|
26
|
+
end
|
27
|
+
|
28
|
+
# Stops counting.
|
29
|
+
def stop
|
30
|
+
return if not @is_running
|
31
|
+
|
32
|
+
@stop_time = Time.now
|
33
|
+
@is_running = false
|
34
|
+
@is_paused = false
|
35
|
+
end
|
36
|
+
|
37
|
+
def restart
|
38
|
+
self.stop
|
39
|
+
self.start
|
40
|
+
end
|
41
|
+
|
42
|
+
def pause
|
43
|
+
return if not @is_running or @is_paused
|
44
|
+
|
45
|
+
@paused_time = (Time.now - @start_time)
|
46
|
+
@is_running = false
|
47
|
+
@is_paused = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def unpause
|
51
|
+
return if not @is_paused or @is_running
|
52
|
+
|
53
|
+
@start_time = (Time.now - @paused_time)
|
54
|
+
@is_running = true
|
55
|
+
@is_paused = false
|
56
|
+
end
|
57
|
+
|
58
|
+
def running?
|
59
|
+
@is_running
|
60
|
+
end
|
61
|
+
|
62
|
+
def paused?
|
63
|
+
@is_paused
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the current delta in seconds (float).
|
67
|
+
def delta
|
68
|
+
if @is_running
|
69
|
+
return (Time.now.to_f - @start_time.to_f)
|
70
|
+
end
|
71
|
+
|
72
|
+
return @paused_time.to_f if @is_paused
|
73
|
+
|
74
|
+
return @start_time if @start_time == 0 # Something's wrong
|
75
|
+
|
76
|
+
return (@stop_time.to_f - @start_time.to_f)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Converts the timer's delta to a formatted string.
|
80
|
+
def to_s
|
81
|
+
min = (self.delta / 60).to_i
|
82
|
+
sec = (self.delta).to_i
|
83
|
+
msec = (self.delta * 100).to_i
|
84
|
+
|
85
|
+
"#{min}:#{sec}:#{msec}"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
@@ -0,0 +1,203 @@
|
|
1
|
+
|
2
|
+
require_relative 'screen.rb'
|
3
|
+
require_relative 'timer.rb'
|
4
|
+
|
5
|
+
# A full guitar tab, as shown on the screen.
|
6
|
+
# Note that it depends on an already-existing window to exist.
|
7
|
+
#
|
8
|
+
class Track
|
9
|
+
COMMENT_CHAR = '#'
|
10
|
+
attr_reader :screen, :percent_completed
|
11
|
+
attr_accessor :speed
|
12
|
+
|
13
|
+
# Creates a Track that will be shown on `screen`.
|
14
|
+
# See Screen.
|
15
|
+
def initialize(screen)
|
16
|
+
@offset = 0
|
17
|
+
@timer = Timer.new
|
18
|
+
@timer.start
|
19
|
+
@speed = 0
|
20
|
+
@screen = screen
|
21
|
+
@percent_completed = 0
|
22
|
+
|
23
|
+
@raw_track = []
|
24
|
+
@raw_track[0] = ""
|
25
|
+
@raw_track[1] = ""
|
26
|
+
@raw_track[2] = ""
|
27
|
+
@raw_track[3] = ""
|
28
|
+
@raw_track[4] = ""
|
29
|
+
@raw_track[5] = ""
|
30
|
+
@raw_track[6] = ""
|
31
|
+
end
|
32
|
+
|
33
|
+
# Loads and parses +filename+'s contents into Track.
|
34
|
+
def load filename
|
35
|
+
if not File.exist? filename
|
36
|
+
raise "Error: File '#{filename}' doesn't exist!"
|
37
|
+
end
|
38
|
+
if not File.file? filename
|
39
|
+
raise "Error: '#{filename}' is not a file!"
|
40
|
+
end
|
41
|
+
|
42
|
+
file = File.new filename
|
43
|
+
|
44
|
+
# The thing here is there's no way I can know in
|
45
|
+
# advance how many lines the tab track will have.
|
46
|
+
#
|
47
|
+
# People put lots of strange things on them like
|
48
|
+
# timing, comments, etecetera.
|
49
|
+
#
|
50
|
+
# So I will read all non-blank lines, creating a
|
51
|
+
# counter. Then I will use it to display the track
|
52
|
+
# onscreen.
|
53
|
+
#
|
54
|
+
# I will also make every line have the same width
|
55
|
+
# as of the biggest one.
|
56
|
+
|
57
|
+
# Any tab line MUST have EITHER ---1---9--| OR |---3----0
|
58
|
+
tab_line = /[-[:alnum:]]\||\|[-[:alnum:]]/
|
59
|
+
|
60
|
+
# Duration of each note only has those chars.
|
61
|
+
# So we look for anything BUT these chars.
|
62
|
+
not_duration_line = /[^WHQESTX \.]/
|
63
|
+
|
64
|
+
count = 0
|
65
|
+
max_width = 0
|
66
|
+
|
67
|
+
file.readlines.each do |line|
|
68
|
+
next if line[0] == COMMENT_CHAR
|
69
|
+
|
70
|
+
line.chomp!
|
71
|
+
if line.empty?
|
72
|
+
|
73
|
+
# Making sure everything will have the same width
|
74
|
+
@raw_track.each_with_index do |t, i|
|
75
|
+
if t.length < max_width
|
76
|
+
@raw_track[i] += (' ' * (max_width - t.length))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
count = 0
|
81
|
+
max_width = 0
|
82
|
+
|
83
|
+
# Lines must be EITHER a tab_line OR a duration_line.
|
84
|
+
# not not duration line means that
|
85
|
+
# (I should find a better way of expressing myself on regexes)
|
86
|
+
elsif (line =~ tab_line) or (not line =~ not_duration_line)
|
87
|
+
@raw_track[count] += line
|
88
|
+
|
89
|
+
if @raw_track[count].length > max_width
|
90
|
+
max_width = @raw_track[count].length
|
91
|
+
end
|
92
|
+
|
93
|
+
count += 1
|
94
|
+
|
95
|
+
end # Ignoring any other kind of line
|
96
|
+
|
97
|
+
if count > 7
|
98
|
+
raise "Error: Invalid format on '#{filename}'"
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Prints the track on the screen, along with string indicators
|
105
|
+
# on the left.
|
106
|
+
#
|
107
|
+
# It is shown at the vertical center of the provided Screen,
|
108
|
+
# spanning it's whole width.
|
109
|
+
def show
|
110
|
+
x = 1
|
111
|
+
y = (@screen.height/2) - (@raw_track.size/2)
|
112
|
+
|
113
|
+
# This both prints EADGBE and clears the whole screen,
|
114
|
+
# printing spaces where the track was.
|
115
|
+
#
|
116
|
+
# Also, if we have only 5 tracks, we leave the sixth
|
117
|
+
# indicator out of the screen.
|
118
|
+
if not @raw_track[6] =~ /[:blank:]/
|
119
|
+
@screen.mvaddstr(0, y, "E" + (' ' * (@screen.width - 1)))
|
120
|
+
@screen.mvaddstr(0, y + 1, "B" + (' ' * (@screen.width - 1)))
|
121
|
+
@screen.mvaddstr(0, y + 2, "G" + (' ' * (@screen.width - 1)))
|
122
|
+
@screen.mvaddstr(0, y + 3, "D" + (' ' * (@screen.width - 1)))
|
123
|
+
@screen.mvaddstr(0, y + 4, "A" + (' ' * (@screen.width - 1)))
|
124
|
+
@screen.mvaddstr(0, y + 5, "E" + (' ' * (@screen.width - 1)))
|
125
|
+
else
|
126
|
+
@screen.mvaddstr(0, y, ' ' * @screen.width)
|
127
|
+
@screen.mvaddstr(0, y + 1, "E" + (' ' * (@screen.width - 1)))
|
128
|
+
@screen.mvaddstr(0, y + 2, "B" + (' ' * (@screen.width - 1)))
|
129
|
+
@screen.mvaddstr(0, y + 3, "G" + (' ' * (@screen.width - 1)))
|
130
|
+
@screen.mvaddstr(0, y + 4, "D" + (' ' * (@screen.width - 1)))
|
131
|
+
@screen.mvaddstr(0, y + 5, "A" + (' ' * (@screen.width - 1)))
|
132
|
+
@screen.mvaddstr(0, y + 6, "E" + (' ' * (@screen.width - 1)))
|
133
|
+
end
|
134
|
+
|
135
|
+
(0...@raw_track.size).each do |i|
|
136
|
+
str = @raw_track[i]
|
137
|
+
str = str[@offset..(@offset + @screen.width - 2)]
|
138
|
+
@screen.mvaddstr(x, y + i, str)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Scrolls the guitar tab by `n`.
|
143
|
+
#
|
144
|
+
# * If `n` is positive, scroll forward.
|
145
|
+
# * If `n` is negative, scroll backward.
|
146
|
+
def scroll n
|
147
|
+
@offset += n
|
148
|
+
|
149
|
+
left_limit = 0
|
150
|
+
right_limit = (@raw_track[0].length - @screen.width + 1).abs
|
151
|
+
|
152
|
+
if @offset < left_limit then @offset = left_limit end
|
153
|
+
if @offset > right_limit then @offset = right_limit end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Goes to the beginning of the Track.
|
157
|
+
def begin
|
158
|
+
@offset = 0
|
159
|
+
@speed = 0
|
160
|
+
end
|
161
|
+
|
162
|
+
# Goes to the end of the Track.
|
163
|
+
def end
|
164
|
+
@offset = (@raw_track[0].length - @screen.width + 1).abs
|
165
|
+
@speed = 0
|
166
|
+
end
|
167
|
+
|
168
|
+
# Turns on/off Track's auto scroll functionality.
|
169
|
+
#
|
170
|
+
# Note that it won't work anyways if you don't keep calling
|
171
|
+
# +update+ method.
|
172
|
+
def auto_scroll option
|
173
|
+
if option == true
|
174
|
+
@timer.start if not @timer.running?
|
175
|
+
else
|
176
|
+
@timer.stop
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Updates Track's auto scroll functionality.
|
181
|
+
def update
|
182
|
+
return if not @timer.running?
|
183
|
+
|
184
|
+
current_completed = @offset + @screen.width - 1
|
185
|
+
@percent_completed = ((100.0 * current_completed)/@raw_track[0].length).ceil
|
186
|
+
|
187
|
+
if @timer.running? and @speed != 0
|
188
|
+
if @timer.delta > (1/(@speed*0.5)).abs
|
189
|
+
if @speed > 0
|
190
|
+
self.scroll 1
|
191
|
+
self.show
|
192
|
+
else
|
193
|
+
self.scroll -1
|
194
|
+
self.show
|
195
|
+
end
|
196
|
+
|
197
|
+
@timer.restart
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
data/lib/tabscroll.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# The main executable file.
|
2
|
+
|
3
|
+
require_relative 'tabscroll/engine'
|
4
|
+
require_relative 'tabscroll/screen'
|
5
|
+
require_relative 'tabscroll/track'
|
6
|
+
require_relative 'tabscroll/popup'
|
7
|
+
|
8
|
+
# Global vars
|
9
|
+
$engine = nil
|
10
|
+
PROGRAM_NAME = "tabscroll"
|
11
|
+
PROGRAM_VERSION = "0.0.1"
|
12
|
+
|
13
|
+
# Terminates program's execution normally, finishing the engine.
|
14
|
+
def quit
|
15
|
+
$engine.exit
|
16
|
+
exit 0
|
17
|
+
end
|
18
|
+
|
19
|
+
# Displays a help window, waiting for a keypress.
|
20
|
+
def show_help_window
|
21
|
+
title = 'Help'
|
22
|
+
text = <<END_OF_TEXT
|
23
|
+
q quit
|
24
|
+
h help/go back
|
25
|
+
left/right auto-scroll left/right
|
26
|
+
up/down stop auto-scrolling
|
27
|
+
</> step scroll left/right
|
28
|
+
o toggle status/title bars
|
29
|
+
|
30
|
+
|
31
|
+
#{PROGRAM_NAME} v#{PROGRAM_VERSION}
|
32
|
+
homepage alexdantas.net/projects/tabscroll
|
33
|
+
author Alexandre Dantas <eu@alexdantas.net>
|
34
|
+
END_OF_TEXT
|
35
|
+
|
36
|
+
pop = Popup.new(title, text)
|
37
|
+
will_quit = pop.show
|
38
|
+
quit if will_quit
|
39
|
+
end
|
40
|
+
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tabscroll
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexandre Dantas
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Scrolls a textual guitar tab on the terminal.
|
14
|
+
email:
|
15
|
+
- eu@alexdantas.net
|
16
|
+
executables:
|
17
|
+
- tabscroll
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/tabscroll.rb
|
22
|
+
- lib/tabscroll/engine.rb
|
23
|
+
- lib/tabscroll/screen.rb
|
24
|
+
- lib/tabscroll/track.rb
|
25
|
+
- lib/tabscroll/popup.rb
|
26
|
+
- lib/tabscroll/timer.rb
|
27
|
+
- bin/tabscroll
|
28
|
+
homepage: http://www.alexdantas.net/projects/tabscroll
|
29
|
+
licenses:
|
30
|
+
- GPL-3.0
|
31
|
+
metadata: {}
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 2.1.7
|
49
|
+
signing_key:
|
50
|
+
specification_version: 4
|
51
|
+
summary: Guitar tab scroller on the terminal
|
52
|
+
test_files: []
|