tabscroll 1.0.1 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/CHANGELOG.md +7 -0
- data/README.md +3 -0
- data/Rakefile +14 -0
- data/bin/tabscroll +2 -2
- data/lib/tabscroll.rb +101 -0
- data/lib/tabscroll/engine.rb +95 -0
- data/lib/tabscroll/popup.rb +64 -0
- data/lib/tabscroll/screen.rb +125 -0
- data/lib/tabscroll/settings.rb +65 -0
- data/lib/tabscroll/timer.rb +89 -0
- data/lib/tabscroll/track.rb +208 -0
- data/lib/tabscroll/version.rb +3 -0
- data/tabscroll.gemspec +32 -0
- metadata +31 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee12d935ef76ceb70ff47c224fb0b9eb03a670b1
|
4
|
+
data.tar.gz: 4d9f7d721e110bd9105329e6524d7a332577f535
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f63aa0639af372c80a70742876933614d22f9596761f0d3c199b0bb1e23881515c7227eccdc5a4b8088de6765003d5d25646d7defefc89488489dc93524e475a
|
7
|
+
data.tar.gz: 8954f7d1eb656939bb70c249470de5dd2ca17c9e6690dc135c71cc6f2466dd646cfde1ff71c5f9cc8f5e6360001cc5c96a225e1589a805a2005d5c1ad37319e2
|
data/.gitignore
ADDED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# tabscroll - Neat guitar tab scroller.
|
2
2
|
|
3
|
+
<!-- gem badge (ignore this if you're in text mode) -->
|
4
|
+
[](http://badge.fury.io/rb/tabscroll)
|
5
|
+
|
3
6
|
`tabscroll` is a (Ruby-powered) guitar tab scroller on the terminal.
|
4
7
|
|
5
8
|
It supports forward and backwards auto-scrolling and currently
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Adding lib directory to load path
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$:.unshift lib unless $:.include? lib
|
4
|
+
|
5
|
+
require 'tabscroll/version'
|
6
|
+
|
7
|
+
task :build do
|
8
|
+
system 'gem build tabscroll.gemspec'
|
9
|
+
end
|
10
|
+
|
11
|
+
task :release => :build do
|
12
|
+
system "gem push tabscroll-#{TabScroll::VERSION}.gem"
|
13
|
+
end
|
14
|
+
|
data/bin/tabscroll
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
# The main `tabscroll` executable
|
4
4
|
|
5
5
|
# Is this considered ugly?
|
6
|
-
|
7
|
-
|
6
|
+
require 'tabscroll'
|
7
|
+
require 'tabscroll/settings'
|
8
8
|
|
9
9
|
# class TabScroll expects this hash, watch out!
|
10
10
|
$settings = {}
|
data/lib/tabscroll.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# The main executable file.
|
2
|
+
|
3
|
+
require 'tabscroll/engine'
|
4
|
+
require 'tabscroll/screen'
|
5
|
+
require 'tabscroll/track'
|
6
|
+
require 'tabscroll/popup'
|
7
|
+
require 'tabscroll/version'
|
8
|
+
|
9
|
+
# The main program.
|
10
|
+
#
|
11
|
+
# Note that we expect a global hash `$settings`.
|
12
|
+
# It contains settings from the commandline argument parser
|
13
|
+
# (Settings class).
|
14
|
+
module TabScroll
|
15
|
+
|
16
|
+
# Executes the whole program, loading contents in `filename`.
|
17
|
+
def self.run filename
|
18
|
+
@engine = Engine.new
|
19
|
+
if not @engine
|
20
|
+
puts 'Failed to start curses!'
|
21
|
+
exit 1337
|
22
|
+
end
|
23
|
+
@engine.timeout 10
|
24
|
+
|
25
|
+
win = Screen.new(0, 1, @engine.width, @engine.height - 2)
|
26
|
+
track = Track.new win
|
27
|
+
track.load filename
|
28
|
+
track.auto_scroll true
|
29
|
+
|
30
|
+
titlebar = Screen.new(0, 0, @engine.width, 1)
|
31
|
+
statusbar = Screen.new(0, (@engine.height - 1), @engine.width, 1)
|
32
|
+
|
33
|
+
bars_hidden = false
|
34
|
+
finished = false
|
35
|
+
while not finished
|
36
|
+
|
37
|
+
# WHY DOES GETCH CLEARS UP THE WHOLE SCREEN?
|
38
|
+
# IT DOESNT MAKE ANY SENSE
|
39
|
+
c = @engine.getchar
|
40
|
+
case c
|
41
|
+
when 'e'
|
42
|
+
track.end
|
43
|
+
when 'a'
|
44
|
+
track.begin
|
45
|
+
when 'h'
|
46
|
+
show_help_window
|
47
|
+
when 'o'
|
48
|
+
bars_hidden = (bars_hidden ? false : true)
|
49
|
+
Curses::clear
|
50
|
+
when '<'
|
51
|
+
track.scroll -5
|
52
|
+
when '>'
|
53
|
+
track.scroll 5
|
54
|
+
when Curses::KEY_LEFT
|
55
|
+
track.speed -= 1
|
56
|
+
when Curses::KEY_RIGHT
|
57
|
+
track.speed += 1
|
58
|
+
when Curses::KEY_DOWN, Curses::KEY_UP
|
59
|
+
track.speed = 0
|
60
|
+
when 'q'
|
61
|
+
$enigne.exit
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
|
65
|
+
track.update
|
66
|
+
track.show
|
67
|
+
if not bars_hidden
|
68
|
+
titlebar.mvaddstr(0, 0, "#{filename} (#{track.percent_completed}%) ", Engine::Colors[:cyan])
|
69
|
+
titlebar.mvaddstr_right(0, " Speed: #{track.speed}", Engine::Colors[:cyan])
|
70
|
+
|
71
|
+
statusbar.mvaddstr_left(0, "tabscroll v#{VERSION} - press `h` for help", Engine::Colors[:green])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
@engine.exit
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# Displays a help window that waits for a keypress.
|
80
|
+
def self.show_help_window
|
81
|
+
title = 'Help'
|
82
|
+
text = <<END_OF_TEXT
|
83
|
+
q quit
|
84
|
+
h help/go back
|
85
|
+
left/right auto-scroll left/right
|
86
|
+
up/down stop auto-scrolling
|
87
|
+
</> step scroll left/right
|
88
|
+
o toggle status/title bars
|
89
|
+
|
90
|
+
|
91
|
+
tabscroll v#{VERSION}
|
92
|
+
homepage alexdantas.net/projects/tabscroll
|
93
|
+
author Alexandre Dantas <eu@alexdantas.net>
|
94
|
+
END_OF_TEXT
|
95
|
+
|
96
|
+
pop = Popup.new(title, text)
|
97
|
+
will_quit = pop.show
|
98
|
+
quit if will_quit
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
@@ -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,64 @@
|
|
1
|
+
|
2
|
+
require 'tabscroll/screen'
|
3
|
+
|
4
|
+
# A simple centralized popup on the terminal.
|
5
|
+
class Popup < Screen
|
6
|
+
|
7
|
+
# Creates a Popup with `title` and inner `text`.
|
8
|
+
#
|
9
|
+
# It resizes to contain the whole text plus a 1x1 border
|
10
|
+
# around itself.
|
11
|
+
def initialize(title, text)
|
12
|
+
@title = title
|
13
|
+
@text = []
|
14
|
+
text.each_line do |line|
|
15
|
+
@text += [line.chomp]
|
16
|
+
end
|
17
|
+
|
18
|
+
max_width = title.length
|
19
|
+
max_height = 1
|
20
|
+
|
21
|
+
@text.each do |line|
|
22
|
+
max_width = line.length if line.length > max_width
|
23
|
+
max_height += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
max_width += 2 # left-right borders
|
27
|
+
max_height += 1 # down border
|
28
|
+
|
29
|
+
x = Curses::cols/2 - max_width/2
|
30
|
+
y = Curses::lines/2 - max_height/2
|
31
|
+
|
32
|
+
super(x, y, max_width, max_height)
|
33
|
+
self.background ' '
|
34
|
+
self.box
|
35
|
+
|
36
|
+
self.mvaddstr_center(0, title, Engine::Colors[:cyan])
|
37
|
+
|
38
|
+
y = 1
|
39
|
+
@text.each do |line|
|
40
|
+
self.mvaddstr(1, y, line)
|
41
|
+
y += 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Makes the Popup appear on the screen and wait for any key.
|
46
|
+
# When it exits, clears the screen erasing itself.
|
47
|
+
def show
|
48
|
+
finished = false
|
49
|
+
while not finished
|
50
|
+
c = Curses::getch
|
51
|
+
case c
|
52
|
+
when 'q'
|
53
|
+
return true
|
54
|
+
when 'h'
|
55
|
+
finished = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Curses::stdscr.clear
|
60
|
+
Curses::stdscr.refresh
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -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,65 @@
|
|
1
|
+
|
2
|
+
require 'optparse'
|
3
|
+
require 'tabscroll'
|
4
|
+
|
5
|
+
# Global configurations of the program, along with a commandline
|
6
|
+
# argument parser.
|
7
|
+
#
|
8
|
+
# Contains the program's specific configuration rules.
|
9
|
+
class Settings
|
10
|
+
|
11
|
+
# Creates a configuration, with default values.
|
12
|
+
def initialize
|
13
|
+
@settings = {}
|
14
|
+
@settings[:filename] = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sets options based on commandline arguments `args`.
|
18
|
+
# It should be `ARGV`.
|
19
|
+
def parse args
|
20
|
+
|
21
|
+
opts = OptionParser.new do |parser|
|
22
|
+
parser.banner = "Usage: tabscroll [options] filename"
|
23
|
+
|
24
|
+
# Make output beautiful
|
25
|
+
parser.separator ""
|
26
|
+
parser.separator "Specific options:"
|
27
|
+
|
28
|
+
parser.on_tail("-h", "--help", "Show this message") do
|
29
|
+
puts parser
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
parser.on_tail("--version", "Show version") do
|
34
|
+
puts "tabscroll v#{TabScroll::VERSION}"
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
end
|
38
|
+
opts.parse! args
|
39
|
+
|
40
|
+
# After parsing all '--args' and '-a' we will check if
|
41
|
+
# the user has provided a filename.
|
42
|
+
#
|
43
|
+
# The first argument without a leading '-' will be
|
44
|
+
# considered.
|
45
|
+
args.each do |arg|
|
46
|
+
if arg =~ /^[^-]/
|
47
|
+
@settings[:filename] = arg
|
48
|
+
break
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if not @settings[:filename]
|
53
|
+
puts opts
|
54
|
+
exit 666
|
55
|
+
end
|
56
|
+
|
57
|
+
return @settings
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns a specific setting previously set.
|
61
|
+
def [] name
|
62
|
+
return @settings[name]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -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
|
+
# Creates the timer, doing nothing else.
|
13
|
+
def initialize
|
14
|
+
@is_running = false
|
15
|
+
@is_paused = false
|
16
|
+
end
|
17
|
+
|
18
|
+
# Starts counting.
|
19
|
+
def start
|
20
|
+
return if @is_running
|
21
|
+
|
22
|
+
@start_time = Time.now
|
23
|
+
@stop_time = 0.0
|
24
|
+
@paused_time = 0.0
|
25
|
+
@is_running = true
|
26
|
+
@is_paused = false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Stops counting.
|
30
|
+
def stop
|
31
|
+
return if not @is_running
|
32
|
+
|
33
|
+
@stop_time = Time.now
|
34
|
+
@is_running = false
|
35
|
+
@is_paused = false
|
36
|
+
end
|
37
|
+
|
38
|
+
def restart
|
39
|
+
self.stop
|
40
|
+
self.start
|
41
|
+
end
|
42
|
+
|
43
|
+
def pause
|
44
|
+
return if not @is_running or @is_paused
|
45
|
+
|
46
|
+
@paused_time = (Time.now - @start_time)
|
47
|
+
@is_running = false
|
48
|
+
@is_paused = true
|
49
|
+
end
|
50
|
+
|
51
|
+
def unpause
|
52
|
+
return if not @is_paused or @is_running
|
53
|
+
|
54
|
+
@start_time = (Time.now - @paused_time)
|
55
|
+
@is_running = true
|
56
|
+
@is_paused = false
|
57
|
+
end
|
58
|
+
|
59
|
+
def running?
|
60
|
+
@is_running
|
61
|
+
end
|
62
|
+
|
63
|
+
def paused?
|
64
|
+
@is_paused
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the current delta in seconds (float).
|
68
|
+
def delta
|
69
|
+
if @is_running
|
70
|
+
return (Time.now.to_f - @start_time.to_f)
|
71
|
+
end
|
72
|
+
|
73
|
+
return @paused_time.to_f if @is_paused
|
74
|
+
|
75
|
+
return @start_time if @start_time == 0 # Something's wrong
|
76
|
+
|
77
|
+
return (@stop_time.to_f - @start_time.to_f)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Converts the timer's delta to a formatted string.
|
81
|
+
def to_s
|
82
|
+
min = (self.delta / 60).to_i
|
83
|
+
sec = (self.delta).to_i
|
84
|
+
msec = (self.delta * 100).to_i
|
85
|
+
|
86
|
+
"#{min}:#{sec}:#{msec}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
@@ -0,0 +1,208 @@
|
|
1
|
+
|
2
|
+
require 'tabscroll/screen'
|
3
|
+
require 'tabscroll/timer'
|
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
|
+
|
10
|
+
# Any line on the file that starts with this char is completely
|
11
|
+
# ignored when processed.
|
12
|
+
# Useful for making use of tabs we currently can't parse.
|
13
|
+
COMMENT_CHAR = '#'
|
14
|
+
|
15
|
+
attr_reader :screen, :percent_completed
|
16
|
+
attr_accessor :speed
|
17
|
+
|
18
|
+
# Creates a Track that will be shown on `screen`.
|
19
|
+
# See Screen.
|
20
|
+
def initialize(screen)
|
21
|
+
@offset = 0
|
22
|
+
@timer = Timer.new
|
23
|
+
@timer.start
|
24
|
+
@speed = 0
|
25
|
+
@screen = screen
|
26
|
+
@percent_completed = 0
|
27
|
+
|
28
|
+
@raw_track = []
|
29
|
+
@raw_track[0] = ""
|
30
|
+
@raw_track[1] = ""
|
31
|
+
@raw_track[2] = ""
|
32
|
+
@raw_track[3] = ""
|
33
|
+
@raw_track[4] = ""
|
34
|
+
@raw_track[5] = ""
|
35
|
+
@raw_track[6] = ""
|
36
|
+
end
|
37
|
+
|
38
|
+
# Loads and parses +filename+'s contents into Track.
|
39
|
+
def load filename
|
40
|
+
if not File.exist? filename
|
41
|
+
raise "Error: File '#{filename}' doesn't exist!"
|
42
|
+
end
|
43
|
+
if not File.file? filename
|
44
|
+
raise "Error: '#{filename}' is not a file!"
|
45
|
+
end
|
46
|
+
|
47
|
+
file = File.new filename
|
48
|
+
|
49
|
+
# The thing here is there's no way I can know in
|
50
|
+
# advance how many lines the tab track will have.
|
51
|
+
#
|
52
|
+
# People put lots of strange things on them like
|
53
|
+
# timing, comments, etecetera.
|
54
|
+
#
|
55
|
+
# So I will read all non-blank lines, creating a
|
56
|
+
# counter. Then I will use it to display the track
|
57
|
+
# onscreen.
|
58
|
+
#
|
59
|
+
# I will also make every line have the same width
|
60
|
+
# as of the biggest one.
|
61
|
+
|
62
|
+
# Any tab line MUST have EITHER ---1---9--| OR |---3----0
|
63
|
+
tab_line = /[-[:alnum:]]\||\|[-[:alnum:]]/
|
64
|
+
|
65
|
+
# Duration of each note only has those chars.
|
66
|
+
# So we look for anything BUT these chars.
|
67
|
+
not_duration_line = /[^WHQESTX \.]/
|
68
|
+
|
69
|
+
count = 0
|
70
|
+
max_width = 0
|
71
|
+
|
72
|
+
file.readlines.each do |line|
|
73
|
+
next if line[0] == COMMENT_CHAR
|
74
|
+
|
75
|
+
line.chomp!
|
76
|
+
if line.empty?
|
77
|
+
|
78
|
+
# Making sure everything will have the same width
|
79
|
+
@raw_track.each_with_index do |t, i|
|
80
|
+
if t.length < max_width
|
81
|
+
@raw_track[i] += (' ' * (max_width - t.length))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
count = 0
|
86
|
+
max_width = 0
|
87
|
+
|
88
|
+
# Lines must be EITHER a tab_line OR a duration_line.
|
89
|
+
# not not duration line means that
|
90
|
+
# (I should find a better way of expressing myself on regexes)
|
91
|
+
elsif (line =~ tab_line) or (not line =~ not_duration_line)
|
92
|
+
@raw_track[count] += line
|
93
|
+
|
94
|
+
if @raw_track[count].length > max_width
|
95
|
+
max_width = @raw_track[count].length
|
96
|
+
end
|
97
|
+
|
98
|
+
count += 1
|
99
|
+
|
100
|
+
end # Ignoring any other kind of line
|
101
|
+
|
102
|
+
if count > 7
|
103
|
+
raise "Error: Invalid format on '#{filename}'"
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Prints the track on the screen, along with string indicators
|
110
|
+
# on the left.
|
111
|
+
#
|
112
|
+
# It is shown at the vertical center of the provided Screen,
|
113
|
+
# spanning it's whole width.
|
114
|
+
def show
|
115
|
+
x = 1
|
116
|
+
y = (@screen.height/2) - (@raw_track.size/2)
|
117
|
+
|
118
|
+
# This both prints EADGBE and clears the whole screen,
|
119
|
+
# printing spaces where the track was.
|
120
|
+
#
|
121
|
+
# Also, if we have only 5 tracks, we leave the sixth
|
122
|
+
# indicator out of the screen.
|
123
|
+
if @raw_track.last =~ /^ *$/
|
124
|
+
@screen.mvaddstr(0, y, "E" + (' ' * (@screen.width - 1)))
|
125
|
+
@screen.mvaddstr(0, y + 1, "B" + (' ' * (@screen.width - 1)))
|
126
|
+
@screen.mvaddstr(0, y + 2, "G" + (' ' * (@screen.width - 1)))
|
127
|
+
@screen.mvaddstr(0, y + 3, "D" + (' ' * (@screen.width - 1)))
|
128
|
+
@screen.mvaddstr(0, y + 4, "A" + (' ' * (@screen.width - 1)))
|
129
|
+
@screen.mvaddstr(0, y + 5, "E" + (' ' * (@screen.width - 1)))
|
130
|
+
else
|
131
|
+
@screen.mvaddstr(0, y, ' ' * @screen.width)
|
132
|
+
@screen.mvaddstr(0, y + 1, "E" + (' ' * (@screen.width - 1)))
|
133
|
+
@screen.mvaddstr(0, y + 2, "B" + (' ' * (@screen.width - 1)))
|
134
|
+
@screen.mvaddstr(0, y + 3, "G" + (' ' * (@screen.width - 1)))
|
135
|
+
@screen.mvaddstr(0, y + 4, "D" + (' ' * (@screen.width - 1)))
|
136
|
+
@screen.mvaddstr(0, y + 5, "A" + (' ' * (@screen.width - 1)))
|
137
|
+
@screen.mvaddstr(0, y + 6, "E" + (' ' * (@screen.width - 1)))
|
138
|
+
end
|
139
|
+
|
140
|
+
(0...@raw_track.size).each do |i|
|
141
|
+
str = @raw_track[i]
|
142
|
+
str = str[@offset..(@offset + @screen.width - 2)]
|
143
|
+
@screen.mvaddstr(x, y + i, str)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Scrolls the guitar tab by `n`.
|
148
|
+
#
|
149
|
+
# * If `n` is positive, scroll forward.
|
150
|
+
# * If `n` is negative, scroll backward.
|
151
|
+
def scroll n
|
152
|
+
@offset += n
|
153
|
+
|
154
|
+
left_limit = 0
|
155
|
+
right_limit = (@raw_track[0].length - @screen.width + 1).abs
|
156
|
+
|
157
|
+
if @offset < left_limit then @offset = left_limit end
|
158
|
+
if @offset > right_limit then @offset = right_limit end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Goes to the beginning of the Track.
|
162
|
+
def begin
|
163
|
+
@offset = 0
|
164
|
+
@speed = 0
|
165
|
+
end
|
166
|
+
|
167
|
+
# Goes to the end of the Track.
|
168
|
+
def end
|
169
|
+
@offset = (@raw_track[0].length - @screen.width + 1).abs
|
170
|
+
@speed = 0
|
171
|
+
end
|
172
|
+
|
173
|
+
# Turns on/off Track's auto scroll functionality.
|
174
|
+
#
|
175
|
+
# Note that it won't work anyways if you don't keep calling
|
176
|
+
# +update+ method.
|
177
|
+
def auto_scroll option
|
178
|
+
if option == true
|
179
|
+
@timer.start if not @timer.running?
|
180
|
+
else
|
181
|
+
@timer.stop
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Updates Track's auto scroll functionality.
|
186
|
+
def update
|
187
|
+
return if not @timer.running?
|
188
|
+
|
189
|
+
current_completed = @offset + @screen.width - 1
|
190
|
+
@percent_completed = ((100.0 * current_completed)/@raw_track[0].length).ceil
|
191
|
+
|
192
|
+
if @timer.running? and @speed != 0
|
193
|
+
if @timer.delta > (1/(@speed*0.5)).abs
|
194
|
+
if @speed > 0
|
195
|
+
self.scroll 1
|
196
|
+
self.show
|
197
|
+
else
|
198
|
+
self.scroll -1
|
199
|
+
self.show
|
200
|
+
end
|
201
|
+
|
202
|
+
@timer.restart
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
data/tabscroll.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Adding lib directory to load path
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$:.unshift lib unless $:.include? lib
|
4
|
+
|
5
|
+
require 'tabscroll/version'
|
6
|
+
require 'date'
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = 'tabscroll'
|
10
|
+
s.version = TabScroll::VERSION
|
11
|
+
s.summary = "Guitar tab scroller on the terminal"
|
12
|
+
s.date = "#{Date.today.year}-#{Date.today.month}-#{Date.today.day}"
|
13
|
+
s.description = <<END_OF_DESCRIPTION
|
14
|
+
Scrolls a textual guitar tab on the terminal.
|
15
|
+
It supports forward and backwards auto-scrolling and currently
|
16
|
+
reads a nice range of guitar tabs.
|
17
|
+
END_OF_DESCRIPTION
|
18
|
+
s.authors = ["Alexandre Dantas"]
|
19
|
+
s.email = ["eu@alexdantas.net"]
|
20
|
+
s.homepage = 'http://www.alexdantas.net/projects/tabscroll'
|
21
|
+
s.license = "GPL-3.0"
|
22
|
+
|
23
|
+
# Including everything that's on `git`
|
24
|
+
s.files = `git ls-files`.split($/)
|
25
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
26
|
+
s.require_paths = ['lib']
|
27
|
+
|
28
|
+
s.metadata = { 'github' => 'http://www.github.com/alexdantas/tabscroll' }
|
29
|
+
|
30
|
+
s.add_development_dependency "rake"
|
31
|
+
end
|
32
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tabscroll
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.40
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre Dantas
|
@@ -9,7 +9,21 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2013-10-10 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
description: |
|
14
28
|
Scrolls a textual guitar tab on the terminal.
|
15
29
|
It supports forward and backwards auto-scrolling and currently
|
@@ -21,10 +35,21 @@ executables:
|
|
21
35
|
extensions: []
|
22
36
|
extra_rdoc_files: []
|
23
37
|
files:
|
24
|
-
-
|
38
|
+
- ".gitignore"
|
39
|
+
- CHANGELOG.md
|
25
40
|
- LICENSE
|
26
41
|
- README.md
|
27
|
-
-
|
42
|
+
- Rakefile
|
43
|
+
- bin/tabscroll
|
44
|
+
- lib/tabscroll.rb
|
45
|
+
- lib/tabscroll/engine.rb
|
46
|
+
- lib/tabscroll/popup.rb
|
47
|
+
- lib/tabscroll/screen.rb
|
48
|
+
- lib/tabscroll/settings.rb
|
49
|
+
- lib/tabscroll/timer.rb
|
50
|
+
- lib/tabscroll/track.rb
|
51
|
+
- lib/tabscroll/version.rb
|
52
|
+
- tabscroll.gemspec
|
28
53
|
homepage: http://www.alexdantas.net/projects/tabscroll
|
29
54
|
licenses:
|
30
55
|
- GPL-3.0
|
@@ -36,12 +61,12 @@ require_paths:
|
|
36
61
|
- lib
|
37
62
|
required_ruby_version: !ruby/object:Gem::Requirement
|
38
63
|
requirements:
|
39
|
-
- -
|
64
|
+
- - ">="
|
40
65
|
- !ruby/object:Gem::Version
|
41
66
|
version: '0'
|
42
67
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
68
|
requirements:
|
44
|
-
- -
|
69
|
+
- - ">="
|
45
70
|
- !ruby/object:Gem::Version
|
46
71
|
version: '0'
|
47
72
|
requirements: []
|