tabscroll 1.0.1 → 1.0.40
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/tabscroll.png)](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: []
|