Skip to content
Browse files

49 specs test OK now. Simple recording + playback verified OK

finished rrts_alsa_driver_spec.rb
new prog: rconnect. literal mapping on aconnect
A second rspec test for Sequencer.

Many improvents to the API too.
I noticed I missed a bunch of subscription functions.

Made progress with very basic Consumer/Producer operations
Added player spec + fixed dreadfull tempo-init bug.

Verified with
	ruby bin/node_identity.rb -i 20:0 -o /tmp/t.yaml
	ruby bin/node_identity.rb -o 20:1 -i /tmp/t.yaml

This can be called a milestone then.
  • Loading branch information...
1 parent 0798afd commit c380f50f79cb8e016d42201764bd9ceadfa96390 @EugeneBrazwick committed Oct 6, 2010
Showing with 4,609 additions and 2,648 deletions.
  1. +6 −6 README
  2. +19 −10 Rakefile
  3. +48 −31 TODOlist
  4. +26 −10 bin/midibox.rb
  5. +18 −20 bin/node_identity.rb
  6. +327 −0 bin/rconnect.rb
  7. +1 −1 bin/rplaymidi++
  8. +2 −2 bin/rrecordmidi++
  9. +1 −0 fixtures/eurodance.yaml
  10. +34 −28 lib/rrts/driver/Makefile
  11. +10 −4 lib/rrts/driver/alsa_client_pool.cpp
  12. +1 −1 lib/rrts/driver/alsa_midi++.cpp
  13. +84 −115 lib/rrts/driver/alsa_midi.cpp
  14. +24 −0 lib/rrts/driver/alsa_midi.h
  15. +2 −2 lib/rrts/driver/alsa_midi_client.cpp
  16. +0 −23 lib/rrts/driver/alsa_midi_client.h
  17. +28 −9 lib/rrts/driver/alsa_midi_event.cpp
  18. +2 −3 lib/rrts/driver/alsa_midi_port.cpp
  19. +39 −6 lib/rrts/driver/alsa_midi_queue.cpp
  20. +7 −6 lib/rrts/driver/alsa_port_subscription.cpp
  21. +363 −0 lib/rrts/driver/alsa_query_subscribe.cpp
  22. +8 −0 lib/rrts/driver/alsa_query_subscribe.h
  23. +129 −11 lib/rrts/driver/alsa_seq.cpp
  24. +2 −0 lib/rrts/driver/alsa_system_info.cpp
  25. +2 −0 lib/rrts/midiclient.rb
  26. +834 −803 lib/rrts/midievent.rb
  27. +16 −7 lib/rrts/midiport.rb
  28. +79 −13 lib/rrts/midiqueue.rb
  29. +165 −152 lib/rrts/node/chunk.rb
  30. +12 −14 lib/rrts/node/mapper.rb
  31. +391 −506 lib/rrts/node/midifilereader.rb
  32. +16 −11 lib/rrts/node/midifilewriter.rb
  33. +315 −344 lib/rrts/node/node.rb
  34. +39 −27 lib/rrts/node/player.rb
  35. +90 −0 lib/rrts/node/splitter.rb
  36. +305 −316 lib/rrts/node/track.rb
  37. +6 −4 lib/rrts/node/yamlreader.rb
  38. +7 −4 lib/rrts/node/yamlwriter.rb
  39. +109 −0 lib/rrts/rrts.rb
  40. +165 −32 lib/rrts/sequencer.rb
  41. +58 −0 lib/rrts/subscription.rb
  42. +3 −3 test/reform_spec.rb
  43. +230 −0 test/rrts_alsa_driver_spec.rb
  44. +173 −0 test/rrts_spec.rb
  45. +8 −8 test/rstore_spec.rb
  46. +405 −116 test/ts_nodes_spec.rb
View
12 README
@@ -9,24 +9,24 @@ In that case, read on:
requirements:
- ruby1.9.2 or higher, including rdoc, gem etc.
- libasound2-dev (on Ubuntu at least), alsa development headers
- - shoulda, testing machinery (sudo gem install shoulda)
- - graphviz, for documentation (optional)
- - the GNU 'make' program
+ - rspec, testing machinery (sudo gem install rspec)
+ - darkfish-rdoc, for documentation (optional)
+ - the GNU 'make' program, cmake and qt4-qmake
- the environment variable $RUBYLIB should contain the 'lib' dir, for example:
export RUBYLIB=/home/eugene/Midibox/lib:/usr/local/lib/site_ruby/1.9.1:/usr/local/lib/site_ruby/1.9.1/x86_64-linux
BUILD: just run
rake
in the toplevel dir.
-INSTALL: cannot be done yet
+INSTALL: cannot be done yet. But isn't really required at the moment.
-Current state (sep 29 2010)
- rjack -> DEAD code
+Current state (oct 6 2010)
Rakefile - seems to do its task OK (if the requirements are met)
lib/rrts/driver - the low level ALSA wrapper. Only contains sequencer/MIDI related API.
But it should work.
lib/rrts - higher level ruby ALSA sequencer interface. The basics are finished.
+ lib/rrts/node - even higher level API based on message passing threads and fibers.
lib/reform - qt GUI declarative interface. Still a running target. Very busy playing and tinkering with it.
bin - contains working examples of literal implementations of C tools like rrecordmidi and rplaymidi,
and the rrts powered versions rrecordmidi++ and rplaymidi++ along asorted goodies like 'panic'
View
29 Rakefile
@@ -8,13 +8,20 @@ rescue
RSPEC = false
end
-begin
- gem 'darkfish-rdoc'
- require 'darkfish-rdoc'
- DARKFISH = true
-rescue
+if RUBY_VERSION =~ /^1\.9\.[01]/
+ begin
+ gem 'darkfish-rdoc'
+ require 'darkfish-rdoc'
+ DARKFISH = true
+ rescue
+ require 'rake/rdoctask'
+ DARKFISH = false
+ end
+else
require 'rake/rdoctask'
DARKFISH = false
+ # apart from that, it still says: 'Generating Darkfish...'
+ # maybe rdoc already uses it if installed. That may explain why it does not work if 'required' by hand
end
ALSALIB='lib/rrts/driver/alsa_midi.so'
@@ -26,23 +33,25 @@ CLOBBER.include('*.log')
# automatically create an rdoc task, + rerdoc [+ clobber_rdoc]
Rake::RDocTask.new do |rd|
- rd.main = 'lib/rrts/rrts.rb'
rd.rdoc_files.include('LICENSE', 'README', '**/*.rb', '**/*.cpp')
- rd.title = 'Midibox'
- rd.options << %q[--exclude="bin/|,v|Makefile|\.yaml|\.css|\.html|\.dot|\.rid|\.log"]
+ rd.options << %q[--exclude="bin/|,v|Makefile|\.yaml|\.css|\.html|\.dot|\.rid|\.log"] <<
+ '--main=lib/rrts/rrts.rb' <<
+ '--title=Midibox'
DARKFISH and
rd.options << '--format=darkfish'
end
if RSPEC
desc "Run all rspec_test"
Spec::Rake::SpecTask.new(:rspec_tests) do |t|
+ t.spec_opts = ['--color']
+ t.ruby_opts = ['-W0']
t.spec_files = FileList['test/**/*_spec.rb']
end
task :test=>:rspec_tests do
- require 'rake/runtest'
- Rake.run_tests 'test/ts_*.rb'
+ # require 'rake/runtest'
+ # Rake.run_tests 'test/**/*_spec.rb'
end
end
View
79 TODOlist
@@ -1,36 +1,37 @@
--0001 wo feb 24 11:40:47 CET 2010 TODO event_output MidiEvent support
- za mrt 6 22:48:43 CET 2010 DONE
--0002 do feb 25 22:22:53 CET 2010 TODO make rplaymidi++ work as a two step system
+-0001 Wed Feb 24 11:40:47 CET 2010 TODO event_output MidiEvent support
+ Sat Mar 6 22:48:43 CET 2010 DONE
+-0002 Thu Feb 25 22:22:53 CET 2010 TODO make rplaymidi++ work as a two step system
a) build an in-memory chunk NODE -> midifile->chunk
b) play the tracks in the chunk NODE -> chunk -> device[s]
- vr mrt 19 21:00:38 CET 2010 more or less, by virtue of midifilereader + -writer.
--0003 za feb 27 21:50:12 CET 2010 BUG rplaymidi reads a NoteOn without a NoteOff
+ Fri Mar 19 21:00:38 CET 2010 more or less, by virtue of midifilereader + -writer.
+-0003 Sat Feb 27 21:50:12 CET 2010 BUG rplaymidi reads a NoteOn without a NoteOff
in fixtures/eurodance.midi. Note 12, Rhythm track. I believe this was recorded
with arecordmidi. So who's to blame? Is there an official midi file dumper somewhere?
- di mrt 16 20:26:02 CET 2010 FIXED. At least in midifilereader.rb
-0004 do mrt 4 23:20:06 CET 2010 BUG rmidiroute.rb:14:in `block in midi_route': undefined method `set_subs'
--0005 do mrt 4 23:24:01 CET 2010 TODO writing ev: param := ':modwheel', reading same event -> param == 1.
+ Tue Mar 16 20:26:02 CET 2010 FIXED. At least in midifilereader.rb
+0004 Thu Mar 4 23:20:06 CET 2010 BUG rmidiroute.rb:14:in `block in midi_route': undefined method `set_subs'
+ NOTE TO SELF: the subscription API was incomplete, but this has been mended.
+-0005 Thu Mar 4 23:24:01 CET 2010 TODO writing ev: param := ':modwheel', reading same event -> param == 1.
This is an inconsistency. Must be mapped back.
- za mrt 6 22:48:43 CET 2010 DONE
--0006 ma mrt 8 21:08:06 CET 2010 BUG emptying the buffers is no good enough to avoid hanging notes.
+ Sat Mar 6 22:48:43 CET 2010 DONE
+-0006 Mon Mar 8 21:08:06 CET 2010 BUG emptying the buffers is no good enough to avoid hanging notes.
NoteOns may already have arrived in a device so we MUST send the NoteOffs.
First make sure we use MidiEvent.
I noticed the following: the INT-handler truly gets first. So we flush the queue.
After that NoteOff's for hanging notes are sent, but to no avail
Finally we get in the ensure of the Sequencer constructor.
No problem, use rb_thread_blocking_region and SignalException
- ma mrt 29 17:29:03 CEST 2010
--0007 do mrt 11 21:39:48 CET 2010 BUG using alsa mallocs causes a critical condition since it uses
+ Mon Mar 29 17:29:03 CEST 2010
+-0007 Thu Mar 11 21:39:48 CET 2010 BUG using alsa mallocs causes a critical condition since it uses
malloc (probably) and not the ruby wrappers. ALLOC or ALLOC_N.
Also a failure should raise NoMemError. The least I can do is make sure these all go
trough one container.
- za mrt 27 21:40:51 CET 2010
--0008 do mrt 11 23:36:01 CET 2010 TODO: queue_timer interface is lacking...
- ma mrt 15 19:00:52 CET 2010 DONE
--0009 ma mrt 15 19:01:17 CET 2010 BUG: merging events through pipes cannot work since notes will
+ Sat Mar 27 21:40:51 CET 2010
+-0008 Thu Mar 11 23:36:01 CET 2010 TODO: queue_timer interface is lacking...
+ Mon Mar 15 19:00:52 CET 2010 DONE
+-0009 Mon Mar 15 19:01:17 CET 2010 BUG: merging events through pipes cannot work since notes will
be out of order.
- ma mrt 15 20:13:17 CET 2010 FIXED
--0010 wo mrt 24 23:41:10 CET 2010 TODO: snd_seq_event_output should signal a possible block.
+ Mon Mar 15 20:13:17 CET 2010 FIXED
+-0010 Wed Mar 24 23:41:10 CET 2010 TODO: snd_seq_event_output should signal a possible block.
Also we should take care in not sending half messages. So we must make sure ahead that there
will be enough space. Compare SYSEX handling where outputbuffer is sometimes increased.
Problem: sometimes we send 2 or 3 events. Could be bad if ev1 is send and then 2 fails.
@@ -40,17 +41,18 @@
All that is left is to use trap on INT etc. to execute remove stuff on the queue.
See miniarp.
The entire way blocking woks has been reviewed
- ma mrt 29 17:29:03 CEST 2010
--0011 za mrt 27 18:18:19 CET 2010 FIXME: I discovered rb_thread_blocking_region.
+ Mon Mar 29 17:29:03 CEST 2010
+-0011 Sat Mar 27 18:18:19 CET 2010 FIXME: I discovered rb_thread_blocking_region.
This means there is really no problem using blocking after all.
Should wrap ALL potentially blocking calls. I don't know for sure if this is the case...
- za mrt 27 21:41:42 CET 2010
-0012 ma mrt 29 17:30:34 CEST 2010 TODO: missing events in alsa_seq.cpp event_output
+ Sat Mar 27 21:41:42 CET 2010
+0012 Mon Mar 29 17:30:34 CEST 2010 TODO: missing events in alsa_seq.cpp event_output
queue messages and alsa connection messages are blatantly missing.
To see this disconnect two ports using kaconnect, and player complains.
-0013 ma mrt 29 17:33:12 CEST 2010 TODO: checking impact GC on realtime filters
+ NOTE TO SELF: these events are sent on port 'SYSTEM ANNOUNCE'. It probably works.
+0013 Mon Mar 29 17:33:12 CEST 2010 TODO: checking impact GC on realtime filters
There is a GC.count method that tells you how many times the GC has run.
-0014 ma mrt 29 21:21:36 CEST 2010 HOWTO implement splitter.
+-0014 Mon Mar 29 21:21:36 CEST 2010 HOWTO implement splitter.
The next task is to split a recording into its separate channels and save these
and then to play them back simultaneously (which is the reverse process).
I) could make a node with an outputconnector per channel and then connect the yamlwriters
@@ -75,7 +77,8 @@
on an event that is possible. The query would obviously be a ruby block.
Indentity can be seen as a Splitter with { |ev| true } as its condition.
A Splitter must be given names + conditions to setup its output structure.
-0015 do apr 1 10:40:41 CEST 2010 TODO: review 'combine' and split options. Currently only active
+ Fri Oct 8 21:28:25 CEST 2010 FIXED
+0015 Thu Apr 1 10:40:41 CEST 2010 TODO: review 'combine' and split options. Currently only active
in Chunk#consume which is stupid and the wrong place. The previous version had major problems
with blocking but that is no longer the case. So this can be done by filters.
Well, 'split_channels' really only applies to Chunk at the moment. All producers
@@ -85,23 +88,37 @@
still delays the original noteon message! So it depends on the situation. And it is not
a big deal if disfunctional! YamlWriter should be able to use it though. And MidiFileReader
could use it. Workaround: use a chunk filter.
-0016 do apr 1 23:10:40 CEST 2010 FIXME: CTRL-C + Player -> small delay shutting down
+0016 Thu Apr 1 23:10:40 CEST 2010 FIXME: CTRL-C + Player -> small delay shutting down
This can be avoided by sending a special InterruptEvent if the caller is interrupted (which
is now more likely then Player itself). In fact Player cannot be interrupted since it calls
each_fiber which does not return (it's weird). On receipt of this event we clean
the queue (but no close yet)
--0017 do apr 1 23:14:14 CEST 2010 BUG: Player + threads deadlocks. But it strange since basicly,
+-0017 Thu Apr 1 23:14:14 CEST 2010 BUG: Player + threads deadlocks. But it's strange since basicly,
there is only one extra thread, because the main process is just calling 'join'.
It may also be event_output hanging. But why? What have threads got to do with this?
It is now certain that event_output() hangs (the wrapper in alsa_midi.so)
It seems that using rb_funcall is a nono for code within a rb_thread_blocking_region
callback. What is likely happening is that the GIL somehow got locked.
This could then be fixed by putting it in a more precise spot. It now embraces too much code,
that would not block anyway.
- vr apr 2 12:24:10 CEST 2010
-0018 vr apr 2 10:36:35 CEST 2010 FIXME: Chunk as a producer does not create the TrackEvents and
+ Fri Apr 2 12:24:10 CEST 2010
+0018 Fri Apr 2 10:36:35 CEST 2010 FIXME: Chunk as a producer does not create the TrackEvents and
the initial tempo may also be missing.
-0019 Wed Sep 29 19:59:26 CEST 2010 BUG: rrecordmidi++ does not understand '-?' for --help
-0020 Wed Sep 29 20:13:13 CEST 2010 bin/node_identity.rb --output='UM-2 MIDI 2' --input=/tmp/t.yaml GIVES:
+-0019 Wed Sep 29 19:59:26 CEST 2010 BUG: rrecordmidi++ does not understand '-?' for --help
+ Wed Oct 6 15:49:47 CEST 2010
+-0020 Wed Sep 29 20:13:13 CEST 2010 bin/node_identity.rb --output='UM-2 MIDI 2' --input=/tmp/t.yaml GIVES:
/home/ara/Midibox/lib/rrts/midiqueue.rb:88:in `set_queue_tempo':
wrong argument type RRTS::Driver::AlsaQueueTempo_i (expected Data) (TypeError)
+ from /home/ara/Midibox/lib/rrts/midiqueue.rb:94:in `tempo='
+ from /home/ara/Midibox/lib/rrts/midiqueue.rb:53:in `initialize'
+ from /home/ara/Midibox/lib/rrts/sequencer.rb:494:in `new'
+ from /home/ara/Midibox/lib/rrts/sequencer.rb:494:in `create_queue'
+ from /home/ara/Midibox/lib/rrts/node/player.rb:145:in `block in consume'
+ from /home/ara/Midibox/lib/rrts/node/node.rb:64:in `block (3 levels) in each_fiber'
+ from /usr/lib/ruby/1.9.1/monitor.rb:201:in `mon_synchronize'
+ from /home/ara/Midibox/lib/rrts/node/node.rb:62:in `block (2 levels) in each_fiber'
+ from /home/ara/Midibox/lib/rrts/node/node.rb:61:in `loop'
+ from /home/ara/Midibox/lib/rrts/node/node.rb:61:in `block in each_fiber'
+ It seems that AlsaQueueTempo_i is never seen as originating from snd_seq_queue_tempo_malloc??
+ Fri Oct 8 21:26:52 CEST 2010 FIXED
+0021 Fri Oct 8 21:26:01 CEST 2010. Change 'skew' consistently into a float.
View
36 bin/midibox.rb
@@ -21,10 +21,25 @@
OPTIONS:
GEMS: darkfish-rdoc
+===ruby1.9.2
+Not the current Ubuntu version, if installed with a reasonable 'configure' then
+the gemdir changes into /usr/lib/ruby/gems, iso /var/lib/gems.
+As a result, all gems disappear and are rebuild, if you don't take precautions.
+I'm alsa getting this error now:
+ Linking CXX shared library libqtruby4shared.so
+/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/4.4.3/../../../../lib/libruby-static.a(array.o): relocation R_X86_64_32 against `.rodata.str1.1' can not be used when making a shared object; recompile with -fPIC
+/usr/lib/gcc/x86_64-linux-gnu/4.4.3/../../../../lib/libruby-static.a: could not read symbols: Bad value
+collect2: ld returned 1 exit status
+make[3]: *** [ruby/qtruby/src/libqtruby4shared.so.2.0.0] Error 1
+Bad linking flags???? No that stupid configure never built any .so's..... WTF?
+./configure --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --enable-shared
+
=end
module Prelims
QTRUBYGEMNAME = 'qtbindings'
PREFIX = ENV['PREFIX'] || '/usr'
+ # another fine mess. If RUBY_VERSION is 1.9.2 you still need the 1.9.1 version tools
+ RUBYVERSION = RUBY_VERSION == '1.9.2' ? '1.9.1' : RUBY_VERSION
class UIHandler
@@ -80,7 +95,7 @@ def self.busy
end
def self.sudo cmd
- STDERR.puts %Q[gksu "#{cmd}"]
+# STDERR.puts %Q[gksu "#{cmd}"]
`gksu "#{cmd}"` && $?.exitstatus == 0
end
@@ -136,7 +151,7 @@ def self.check_exe_and_opt_apt_get(binary, package, target) # for example: 'gem'
# puts "which binary -> " + `which #{binary}`
while (cmd = `which #{binary}`.chomp).empty?
# puts "attempt two. But a bit tricky with some binaries"
- cmd = `ls "#{PREFIX}"/bin/#{binary}* | grep '/#{binary}[0-9\.]*$' | \
+ cmd = `ls "#{PREFIX}"/bin/#{binary}* 2>/dev/null | grep '/#{binary}[0-9\.]*$' | \
sort --general-numeric-sort | tail --lines 1`.chomp
# puts "cmd = #{cmd.inspect}"
return cmd unless cmd.empty?
@@ -166,10 +181,10 @@ def self.check_gem(binary, package, target, params = {}) # for example: 'spec',
def self.prelims
# First task. Check whether the RUBY version is suitable.
- major, minor, patch = RUBY_VERSION.split('.').map(&:to_i)
+ major, minor, patch = RUBYVERSION.split('.').map(&:to_i)
if major < 1 || major == 1 && minor < 9
# No use trying to fix this, since the ../midibox script should already have done so.
- @@handler::die 1, "Your ruby version (#{RUBY_VERSION}) is too low. 1.9 is required!\n" +
+ @@handler::die 1, "Your ruby version (#{RUBYVERSION}) is too low. 1.9 is required!\n" +
"If you have it, but $RUBY points to a lower version, please adjust $RUBY"
end
@@ -182,7 +197,7 @@ def self.prelims
And may become rubygems1.9 as well if we move ahead to 1.9.3
=end
- @@gemcmd = check_exe_and_opt_apt_get('gem', "rubygems#{RUBY_VERSION}", 'qtruby')
+ @@gemcmd = check_exe_and_opt_apt_get('gem', "rubygems#{RUBYVERSION}", 'qtruby')
# Qt must work...
begin
@@ -200,9 +215,10 @@ def self.prelims
ENV['CXX'] = check_exe_and_opt_apt_get('g++', 'g++', 'qtruby')
ENV['QMAKE'] = check_exe_and_opt_apt_get('qmake', 'qt4-qmake', 'qtruby')
# I overlooked one somehow. Ruby-dev is required for /usr/include/ruby/ruby.h
- check_libdev_and_opt_apt_get("include/ruby-#{RUBY_VERSION}/ruby.h", "ruby#{RUBY_VERSION}-dev", 'qtruby')
+ check_libdev_and_opt_apt_get("include/ruby-#{RUBYVERSION}/ruby.h", "ruby#{RUBYVERSION}-dev", 'qtruby')
check_libdev_and_opt_apt_get('include/qt4/Qt/qglobal.h', 'libqt4-dev', 'qtruby')
check_gem(nil, QTRUBYGEMNAME, 'qtruby')
+ retry # !
end
=begin
@@ -226,7 +242,7 @@ def self.prelims
# We need the rspec gem first.
ENV['RSPEC'] = check_gem('spec', 'rspec', 'alsa_midi.so')
check_gem(nil, 'darkfish-rdoc', 'htmldocs', optional: true)
- check_gem(nil, 'shoulda', 'testing', optional: true)
+# check_gem(nil, 'shoulda', 'testing', optional: true) no longer used
check_libdev_and_opt_apt_get('include/alsa/asoundlib.h', 'libasound2-dev', 'alsa_midi.so')
@@handler::busy do
`"#{@@rakecmd}"`
@@ -244,7 +260,7 @@ def self.prelims_and_spectest
Qt::Application.new([]) unless $qApp
if @@handler::yesno 'Stuff was build', "Test freshly made software now?"
`'#{@@rakecmd}' test`
- @@handler::die 7, 'spectest failed' unless $?.exitstatus == 0
+ @@handler::die 7, 'rake test failed' unless $?.exitstatus == 0
end
if $qApp
$qApp.quit
@@ -255,11 +271,11 @@ def self.prelims_and_spectest
def self.check_reqs
# just a list of all checks, for debugging purposes. (use bin/midibox.rb --check-reqs)
- check_exe_and_opt_apt_get('gem', "rubygems#{RUBY_VERSION}", 'qtruby')
+ check_exe_and_opt_apt_get('gem', "rubygems#{RUBYVERSION}", 'qtruby')
check_exe_and_opt_apt_get('cmake', 'cmake', 'qtruby')
check_exe_and_opt_apt_get('g++', 'g++', 'qtruby')
check_exe_and_opt_apt_get('qmake', 'qt4-qmake', 'qtruby')
- check_libdev_and_opt_apt_get("include/ruby-#{RUBY_VERSION}/ruby.h", "ruby#{RUBY_VERSION}-dev", 'qtruby')
+ check_libdev_and_opt_apt_get("include/ruby-#{RUBYVERSION}/ruby.h", "ruby#{RUBYVERSION}-dev", 'qtruby')
check_libdev_and_opt_apt_get('include/qt4/Qt/qglobal.h', 'libqt4-dev', 'qtruby')
check_gem(nil, QTRUBYGEMNAME, 'qtruby')
check_gem('spec', 'rspec', 'alsa_midi.so')
View
38 bin/node_identity.rb
@@ -4,7 +4,7 @@ module RRTS
module Nodes
- require_relative '../lib/rrts/node/node'
+ require 'rrts/node/node'
=begin SOME THOUGHTS
@@ -14,25 +14,23 @@ module Nodes
Or we should 'dip in' the namespace and use X instead of recreating it.
=end
-=begin rdoc
-
-Identity is a class that just attaches an input to an output.
-Functionality is gained by using specific input- and outputoptions
-
-Examples:
- Identity.new '--input=song.yaml', '--output='20:1'
- Identity.new '--input=UM-2 MIDI 1', '--output=song.yaml.gz'
- Identity.new '--input=song.midi', '--output=song.ygz'
-
-Extensions understood:
- .mid
- .midi
- .yaml
- .yaml.gz
- .ygz
-
-Anything else is treated as a portidentifier.
-=end
+#
+# Identity is a class that just attaches an input to an output.
+# Functionality is gained by using specific input- and outputoptions
+#
+# Examples:
+# Identity.new '--input=song.yaml', '--output='20:1'
+# Identity.new '--input=UM-2 MIDI 1', '--output=song.yaml.gz'
+# Identity.new '--input=song.midi', '--output=song.ygz'
+#
+# Extensions understood:
+# .mid
+# .midi
+# .yaml
+# .yaml.gz
+# .ygz
+#
+# Anything else is treated as a portidentifier.
class Identity < Node::Filter
private
def initialize *options
View
327 bin/rconnect.rb
@@ -0,0 +1,327 @@
+=begin
+================================================================
+ aconnect - control subscriptions
+ ver.0.1.3
+ Copyright (C) 1999-2000 Takashi Iwai
+================================================================
+=end
+
+=begin
+/*
+ * connect / disconnect two subscriber ports
+ * ver.0.1.3
+ *
+ * Copyright (C) 1999 Takashi Iwai
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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.
+ *
+ */
+=end
+
+require 'rrts/driver/alsa_midi'
+
+include RRTS::Driver
+
+# translation stub....
+def _(x)
+ x
+end
+
+def usage
+ printf(_("rconnect - ALSA sequencer connection manager\n"));
+ printf(_("Copyright (C) 1999-2000 Takashi Iwai\n"));
+ printf(_("Copyright (c) 2010 Eugene Brazwick\n"));
+ printf(_("Usage:\n"));
+ printf(_(" * Connection/disconnection between two ports\n"));
+ printf(_(" aconnect [-options] sender receiver\n"));
+ printf(_(" sender, receiver = client:port pair\n"));
+ printf(_(" -d,--disconnect disconnect\n"));
+ printf(_(" -e,--exclusive exclusive connection\n"));
+ printf(_(" -r,--real # convert real-time-stamp on queue\n"));
+ printf(_(" -t,--tick # convert tick-time-stamp on queue\n"));
+ printf(_(" * List connected ports (no subscription action)\n"));
+ printf(_(" aconnect -i|-o [-options]\n"));
+ printf(_(" -i,--input list input (readable) ports\n"));
+ printf(_(" -o,--output list output (writable) ports\n"));
+ printf(_(" -l,--list list current connections of each port\n"));
+ printf(_(" * Remove all exported connections\n"));
+ printf(_(" -x, --removeall\n"));
+end
+
+# /*
+# * check permission (capability) of specified port
+# */
+
+
+LIST_INPUT = 1
+LIST_OUTPUT = 2
+
+def perm_ok(pinfo, bits)
+ (pinfo.capability & bits) == bits
+end
+
+def check_permission pinfo, perm
+ catch :ok do
+ if (perm != 0)
+ if (perm & LIST_INPUT) != 0
+ throw :ok if (perm_ok(pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ))
+ end
+ if (perm & LIST_OUTPUT) != 0
+ throw :ok if (perm_ok(pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
+ end
+ return false
+ end
+ end
+ (pinfo.capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0
+end
+
+# /*
+# * list subscribers of specified type
+# */
+def list_each_subs seq, subs, type, msg
+ count = 0;
+ subs.type = type
+ subs.index = 0
+ while seq.query_port_subscribers(subs)
+ if (count == 0)
+ printf("\t%s: ", msg);
+ else
+ printf(", ");
+ end
+ addr = subs.addr
+ printf("%d:%d", addr[0], addr[1]);
+ printf(", root=%d:%d" % subs.root);
+ printf("[ex]") if subs.exclusive?
+ if subs.time_update?
+ printf("[%s:%d]",
+ subs.time_real? ? "real" : "tick",
+ subs.queue)
+ end
+ subs.index += 1
+ count += 1
+ end
+ print "\n" if (count > 0)
+end
+
+# /*
+# * list subscribers
+# */
+def list_subscribers(seq, addr)
+ subs = query_subscribe_malloc
+# snd_seq_query_subscribe_alloca(&subs);
+ subs.root = addr
+ list_each_subs(seq, subs, SND_SEQ_QUERY_SUBS_READ, _("Connecting To"));
+ list_each_subs(seq, subs, SND_SEQ_QUERY_SUBS_WRITE, _("Connected From"));
+end
+
+# /*
+# * search all ports
+# */
+# typedef void (*action_func_t)(snd_seq_t *seq, snd_seq_client_info_t *cinfo, snd_seq_port_info_t *pinfo, int count);
+
+def do_search_port(seq, perm, &do_action)
+# snd_seq_client_info_t *cinfo;
+# snd_seq_port_info_t *pinfo;
+# int count;
+
+ cinfo = client_info_malloc
+ pinfo = port_info_malloc
+ cinfo.client = -1
+ while (seq.query_next_client(cinfo))
+# /* reset query info */
+ pinfo.client = cinfo.client
+ pinfo.port = -1
+ count = 0
+ while (seq.query_next_port(pinfo))
+ if (check_permission(pinfo, perm))
+ do_action.call(seq, cinfo, pinfo, count);
+ count += 1
+ end
+ end
+ end
+end
+
+# callbacks, C style!
+def print_port(seq, cinfo, pinfo, count)
+ if (count == 0)
+ printf(_("client %d: '%s' [type=%s]\n"),
+ cinfo.client, cinfo.name, cinfo.type == SND_SEQ_USER_CLIENT ? _("user") : _("kernel"));
+ end
+ printf(" %3d '%-16s'\n", pinfo.port, pinfo.name)
+end
+
+def print_port_and_subs(seq, cinfo, pinfo, count)
+ print_port(seq, cinfo, pinfo, count);
+ list_subscribers(seq, pinfo.addr)
+end
+
+
+# /*
+# * remove all (exported) connections
+# */
+def remove_connection(seq, cinfo, pinfo, count)
+
+ query = query_subscribe_malloc
+# snd_seq_query_subscribe_alloca(&query);
+ query.root = pinfo.addr
+ query.type = SND_SEQ_QUERY_SUBS_READ
+ query.index = 0
+ while seq.query_port_subscribers(query)
+# snd_seq_port_info_t *port;
+# snd_seq_port_subscribe_t *subs;
+ sender = query.root
+ dest = query.addr
+ begin
+ port = seq.any_port_info(dest[0], dest[1])
+ if (port.capability & SND_SEQ_PORT_CAP_SUBS_WRITE) != 0 &&
+ (port.capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0
+ subs = port_subscribe_malloc
+ subs.queueu = query.queue
+ sub.sender = sender
+ sub.dest = dest
+ seq.unsubscribe_port(subs);
+ end
+ rescue AlsaMidiError=>e
+ STDERR.puts "WARNING: #{e}"
+ end
+ query.index += 1
+ end
+
+ query.type = SND_SEQ_QUERY_SUBS_WRITE
+ query.index = 0
+ while seq.query_port_subscribers(query)
+ dest = query.root
+ sender = query.addr
+ begin
+ port = seq.any_port_info(sender[0], sender[1])
+ if (port.capability & SND_SEQ_PORT_CAP_SUBS_READ) != 0 &&
+ (port.capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0
+ subs = port_subscribe_malloc
+ subs.queue = query.queue
+ subs.sender = sender
+ subs.dest = dest
+ seq.unsubscribe_port(subs);
+ end
+ rescue AlsaMidiError=>e
+ STDERR.puts "WARNING: #{e}"
+ end
+ query.index += 1
+ end
+end
+
+def remove_all_connections(seq)
+ do_search_port(seq, 0) { |seq2, c, p, count| remove_connection(seq2, c, p, count) }
+end
+
+
+# /*
+# * main..
+# */
+
+SUBSCRIBE = 0
+UNSUBSCRIBE = 1
+LIST = 2
+REMOVE_ALL = 3
+$PROGRAM_NAME = 'rconnect'
+
+# int c;
+# snd_seq_t *seq;
+queue = 0
+convert_time = convert_real = exclusive = false
+
+command = SUBSCRIBE;
+list_perm = 0
+# int client;
+list_subs = false
+# snd_seq_port_subscribe_t *subs;
+# snd_seq_addr_t sender, dest;
+
+# setlocale(LC_ALL, "");
+# textdomain(PACKAGE);
+
+require 'optparse'
+opts = OptionParser.new
+opts.banner = "Usage: #$PROGRAM_NAME [options] [sender] [receiver]"
+opts.on('-h', '--help', 'this help') { usage; exit 1; }
+opts.on('-d', '--disconnect', 'disconnect sender from receiver') { command = UNSUBSCRIBE }
+opts.on('-i', '--input', 'list input ports') { command = LIST; list_perm |= LIST_INPUT }
+opts.on('-o', '--output', 'list output ports') { command = LIST; list_perm |= LIST_OUTPUT }
+opts.on('-l', '--list', 'list correct connections (use with -i/-o)') { list_subs = true }
+opts.on('-r', '--real=QUEUE', 'convert realtime timestamps', Integer) { |q| queue = q; convert_time = convert_real = true }
+opts.on('-t', '--tick=QUEUE', 'convert tick timestamps', Integer) { |q| queue = q; convert_time = true; convert_real = false }
+opts.on('-e', '--exclusive', 'make an exclusive connection') { exclusive = true }
+opts.on('-x', '--removeall', 'remove all exported connections') { command = REMOVE_ALL }
+
+sender_receiver = opts.parse ARGV
+
+seq = seq_open("default", SND_SEQ_OPEN_DUPLEX, 0)
+
+snd_lib_error_set_handler do |file, line, function, err, text|
+ STDERR.puts "error CALLBACK triggered!!"
+ if (err != Errno::ENOENT::Errno) # Ignore those misleading "warnings"
+ s = "ALSA lib %s:%i:(%s) " % [file, line, function]
+ s += text
+ s += ": %s" % snd_strerror(err) if err != 0
+ s += "\n"
+ raise AlsaMidiError, s
+ end
+end
+
+case command
+when LIST
+ do_search_port(seq, list_perm) do |l_seq, cinfo, pinfo, count|
+ if list_subs then print_port_and_subs(l_seq, cinfo, pinfo, count)
+ else print_port(l_seq, cinfo, pinfo, count)
+ end
+ end
+ seq.close
+ exit 0
+when REMOVE_ALL
+ remove_all_connections(seq);
+ seq.close
+ exit 0
+end
+
+# /* connection or disconnection */
+
+if sender_receiver.length != 2
+ seq.close
+ usage
+ exit(1);
+end
+
+client = seq.client_id
+# /* set client info */
+seq.client_name = "ALSA Connector"
+
+# /* set subscription */
+sender = seq.parse_address(sender_receiver[0])
+dest = seq.parse_address(sender_receiver[1])
+subs = port_subscribe_malloc
+subs.sender = sender
+subs.dest = dest
+subs.queue = queue
+subs.exclusive = exclusive
+subs.time_update = convert_time
+subs.time_real = convert_real
+
+if (command == UNSUBSCRIBE)
+ seq.unsubscribe_port(subs) if seq.get_port_subscription(subs)
+else
+ if seq.port_subscription?(subs)
+ seq.close
+ SRDERR.printf(_("Connection is already subscribed\n"));
+ exit 1;
+ end
+ seq.subscribe_port subs
+end
+
+seq.close
+
View
2 bin/rplaymidi++
@@ -472,7 +472,7 @@ SND_UTIL_VERSION_STR = '1.0'
require 'optparse'
opts = OptionParser.new
opts.banner = "Usage: #$PROGRAM_NAME [options] inputfile ..."
-opts.on('-h', '--help', 'this help') { puts opts.to_s; exit 1; }
+opts.on('-h', '-?', '--help', 'this help') { puts opts.to_s; exit 1; }
opts.on('-V', '--version', 'show version') do
puts "rplaymidi version " + SND_UTIL_VERSION_STR
exit 0
View
4 bin/rrecordmidi++
@@ -418,7 +418,7 @@ end
require 'optparse'
opts = OptionParser.new
opts.banner = "Usage: #$PROGRAM_NAME [options] outputfile|-"
-opts.on('-h', '--help', 'this help') { puts opts.to_s; exit 1 }
+opts.on('-h', '-?', '--help', 'this help') { puts opts.to_s; exit 1 }
opts.on('-V', '--version', 'show version') { STDERR.puts("rrecordmidi version #{SND_UTIL_VERSION_STR}"); exit }
opts.on('-l', '--list', 'list input ports') do
puts(" Port Client name Port name")
@@ -497,7 +497,7 @@ opts.on('-s', '--[no-]channel-split') { |val| @channel_split = val }
# trace
# open sequencer
-require_relative '../lib/rrts/sequencer'
+require 'rrts/sequencer'
include RRTS
Sequencer.new('rrecordmidi') do |seq|
@sequencer = seq # export ....
View
1 fixtures/eurodance.yaml
@@ -66,6 +66,7 @@ flags:
:time_mode_ticks: true
num: 4
param:
+something: 8
source:
time: 0
track: 1
View
62 lib/rrts/driver/Makefile
@@ -9,20 +9,22 @@ hdrdir = /usr/include/ruby-1.9.1
arch_hdrdir = /usr/include/ruby-1.9.1/$(arch)
VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby
prefix = $(DESTDIR)/usr
-exec_prefix = $(prefix)
+rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
+exec_prefix = $(DESTDIR)/usr
vendorhdrdir = $(rubyhdrdir)/vendor_ruby
sitehdrdir = $(rubyhdrdir)/site_ruby
-rubyhdrdir = $(includedir)/ruby-$(ruby_version)
-vendordir = $(DESTDIR)/usr/lib/ruby/vendor_ruby
-sitedir = $(DESTDIR)/usr/local/lib/site_ruby
-mandir = $(prefix)/share/man
+rubyhdrdir = $(includedir)/$(RUBY_BASE_NAME)-$(ruby_version)
+vendordir = $(rubylibprefix)/vendor_ruby
+sitedir = $(rubylibprefix)/site_ruby
+ridir = $(datarootdir)/$(RI_BASE_NAME)
+mandir = $(datarootdir)/man
localedir = $(datarootdir)/locale
libdir = $(exec_prefix)/lib
psdir = $(docdir)
pdfdir = $(docdir)
dvidir = $(docdir)
htmldir = $(docdir)
-infodir = $(prefix)/share/info
+infodir = $(datarootdir)/info
docdir = $(datarootdir)/doc/$(PACKAGE)
oldincludedir = $(DESTDIR)/usr/include
includedir = $(prefix)/include
@@ -31,10 +33,10 @@ sharedstatedir = $(prefix)/com
sysconfdir = $(DESTDIR)/etc
datadir = $(datarootdir)
datarootdir = $(prefix)/share
-libexecdir = $(prefix)/lib/ruby1.9.1
+libexecdir = $(exec_prefix)/libexec
sbindir = $(exec_prefix)/sbin
bindir = $(exec_prefix)/bin
-rubylibdir = $(libdir)/ruby/$(ruby_version)
+rubylibdir = $(rubylibprefix)/$(ruby_version)
archdir = $(rubylibdir)/$(arch)
sitelibdir = $(sitedir)/$(ruby_version)
sitearchdir = $(sitelibdir)/$(sitearch)
@@ -45,41 +47,42 @@ CC = gcc
CXX = g++
LIBRUBY = $(LIBRUBY_SO)
LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
-LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
-LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static
+LIBRUBYARG_SHARED = -Wl,-R -Wl,$(libdir) -L$(libdir) -l$(RUBY_SO_NAME)
+LIBRUBYARG_STATIC = -Wl,-R -Wl,$(libdir) -L$(libdir) -l$(RUBY_SO_NAME)-static
OUTFLAG = -o
COUTFLAG = -o
RUBY_EXTCONF_H =
-cflags = $(optflags) $(debugflags) $(warnflags)
-optflags = -O2
-debugflags = -g
-warnflags = -Wall -Wno-parentheses
-CFLAGS = -fPIC -fno-strict-aliasing -g -g -O2 $(cflags) -fPIC
+cflags = $(optflags) $(debugflags) $(warnflags)
+optflags = -O3
+debugflags = -ggdb
+warnflags = -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long
+CFLAGS = -fPIC $(cflags) -fPIC
INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
DEFS =
CPPFLAGS = $(DEFS) $(cppflags)
-CXXFLAGS = $(CFLAGS) -fno-strict-aliasing -g $(cxxflags)
-ldflags = -L. -Wl,-Bsymbolic-functions -rdynamic -Wl,-export-dynamic
+CXXFLAGS = $(CFLAGS) $(cxxflags)
+ldflags = -L. -rdynamic -Wl,-export-dynamic
dldflags =
-archflag =
-DLDFLAGS = $(ldflags) $(dldflags) $(archflag)
+ARCH_FLAG =
+DLDFLAGS = $(ldflags) $(dldflags)
LDSHARED = $(CC) -shared
LDSHAREDXX = $(CXX) -shared
AR = ar
EXEEXT =
-RUBY_INSTALL_NAME = ruby1.9.1
-RUBY_SO_NAME = ruby-1.9.1
+RUBY_BASE_NAME = ruby
+RUBY_INSTALL_NAME = ruby
+RUBY_SO_NAME = ruby
arch = x86_64-linux
-sitearch = x86_64-linux
+sitearch = $(arch)
ruby_version = 1.9.1
-ruby = /usr/bin/ruby1.9.1
+ruby = /usr/bin/ruby
RUBY = $(ruby)
RM = rm -f
RM_RF = $(RUBY) -run -e rm -- -rf
RMDIRS = $(RUBY) -run -e rmdir -- -p
-MAKEDIRS = mkdir -p
+MAKEDIRS = /bin/mkdir -p
INSTALL = /usr/bin/install -c
INSTALL_PROG = $(INSTALL) -m 0755
INSTALL_DATA = $(INSTALL) -m 644
@@ -90,7 +93,7 @@ COPY = cp
preload =
libpath = . $(libdir)
-LIBPATH = -L. -L$(libdir)
+LIBPATH = -L. -L$(libdir) -Wl,-R$(libdir)
DEFFILE =
CLEANFILES = mkmf.log
@@ -102,8 +105,8 @@ extout_prefix =
target_prefix =
LOCAL_LIBS =
LIBS = $(LIBRUBYARG_SHARED) -lasound -lpthread -lrt -ldl -lcrypt -lm -lc
-SRCS = alsa_midi_port.cpp alsa_midi_timer.cpp alsa_midi++.cpp alsa_midi_queue.cpp alsa_seq.cpp alsa_remove.cpp alsa_client_pool.cpp alsa_midi.cpp alsa_system_info.cpp alsa_midi_client.cpp alsa_port_subscription.cpp alsa_midi_event.cpp
-OBJS = alsa_midi_port.o alsa_midi_timer.o alsa_midi++.o alsa_midi_queue.o alsa_seq.o alsa_remove.o alsa_client_pool.o alsa_midi.o alsa_system_info.o alsa_midi_client.o alsa_port_subscription.o alsa_midi_event.o
+SRCS = alsa_midi_port.cpp alsa_midi_timer.cpp alsa_midi++.cpp alsa_midi_queue.cpp alsa_seq.cpp alsa_remove.cpp alsa_client_pool.cpp alsa_midi.cpp alsa_system_info.cpp alsa_query_subscribe.cpp alsa_midi_client.cpp alsa_port_subscription.cpp alsa_midi_event.cpp
+OBJS = alsa_midi_port.o alsa_midi_timer.o alsa_midi++.o alsa_midi_queue.o alsa_seq.o alsa_remove.o alsa_client_pool.o alsa_midi.o alsa_system_info.o alsa_query_subscribe.o alsa_midi_client.o alsa_port_subscription.o alsa_midi_event.o
TARGET = alsa_midi
DLLIB = $(TARGET).so
EXTSTATIC =
@@ -122,6 +125,8 @@ CLEANOBJS = *.o *.bak
all: $(DLLIB)
static: $(STATIC_LIB)
+.PHONY: all install static install-so install-rb
+.PHONY: clean clean-so clean-rb
clean-rb-default::
clean-rb::
@@ -143,7 +148,8 @@ install: install-so install-rb
install-so: $(RUBYARCHDIR)
install-so: $(RUBYARCHDIR)/$(DLLIB)
$(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
- $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
+ @-$(MAKEDIRS) $(@D)
+ $(INSTALL_PROG) $(DLLIB) $(@D)
install-rb: pre-install-rb install-rb-default
install-rb-default: pre-install-rb-default
pre-install-rb: Makefile
View
14 lib/rrts/driver/alsa_client_pool.cpp
@@ -30,7 +30,10 @@ wrap_snd_seq_client_pool_get_client(VALUE v_pool)
/** call-seq: output_pool() -> int
-Returns: the output pool size
+Returns: the output pool size. This is the total kernelspace reserved for this client for
+buffering events sent to other clients.
+This is a pool in the sense that ports share this space. If a single port fills the
+outputpool then other ports cannot write either.
*/
static VALUE
wrap_snd_seq_client_pool_get_output_pool(VALUE v_pool)
@@ -42,7 +45,8 @@ wrap_snd_seq_client_pool_get_output_pool(VALUE v_pool)
/** call-seq: input_pool() -> int
-Returns: Get the input pool size
+Returns: Get the input pool size. This is the total amount of kernelspace
+for events waiting to be read by a client.
*/
static VALUE
wrap_snd_seq_client_pool_get_input_pool(VALUE v_pool)
@@ -66,7 +70,7 @@ wrap_snd_seq_client_pool_get_output_room(VALUE v_pool)
/** call-seq: output_free() -> int
-Returns: the available size on the output pool
+Returns: the available free space on the output pool in bytes
*/
static VALUE
wrap_snd_seq_client_pool_get_output_free(VALUE v_pool)
@@ -116,7 +120,9 @@ wrap_snd_seq_client_pool_set_input_pool(VALUE v_pool, VALUE v_sz)
/** call-seq output_room = size
-Set the output room size
+Set the output room size. According to Eugene this is the watermark to wake up a
+client that got blocked when writing data, because the output pool was full.
+
*/
static VALUE
wrap_snd_seq_client_pool_set_output_room(VALUE v_pool, VALUE v_sz)
View
2 lib/rrts/driver/alsa_midi++.cpp
@@ -34,7 +34,7 @@ alsaMidiEventClass_populate(VALUE v_ev, VALUE v_sequencer, VALUE v_midievent)
// required to locate or construct ports and queues, but maybe the caller should do this?
// 2, read out snd_seq_event_t and use instance_variable_set on v_midievent
// fprintf(stderr, "ev.type=%d, evtpnam='%s', CLOCK=%d\n", ev->type, evtypename[ev->type] ? evtypename[ev->type] : "null", SND_SEQ_EVENT_CLOCK);
- if (ev->type < 256 && evtypename[ev->type])
+ if (/* unsigned char so always true: ev->type < 256 && */evtypename[ev->type])
{
const char * const nam = evtypename[ev->type]; // enum SYSTEM .. NONE. Each has a specific datatype
// fprintf(stderr, "using symbol '%s'\n", nam);
View
199 lib/rrts/driver/alsa_midi.cpp
@@ -27,6 +27,7 @@ initialization method that defines all other classes.
#include "alsa_client_pool.h"
#include "alsa_system_info.h"
#include "alsa_midi_timer.h"
+#include "alsa_query_subscribe.h"
#if defined(DUMP_API)
#define DUMP_STREAM stderr
@@ -106,6 +107,22 @@ wrap_snd_seq_client_info_malloc(VALUE v_module)
XMALLOC(snd_seq_client_info));
}
+/** call-seq: query_subscribe_malloc() -> AlsaQuerySubscribe_i
+
+Returns: an empty AlsaQuerySubscribe_i instance.
+
+The returned instance is automatically freed when the object goes out of scope.
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_malloc(VALUE v_module)
+{
+#if defined(DUMP_API)
+ fprintf(DUMP_STREAM, "snd_seq_query_subscribe_malloc(null)\n");
+#endif
+ return Data_Wrap_Struct(alsaQuerySubscribeClass, 0/*mark*/, snd_seq_query_subscribe_free/*free*/,
+ XMALLOC(snd_seq_query_subscribe));
+}
+
/** call-seq: system_info_malloc() -> AlsaSystemInfo_i
Returns: a new, empty AlsaSystemInfo_i instance which is automatically freed
@@ -235,7 +252,7 @@ wrap_snd_seq_client_pool_malloc(VALUE v_mod)
XMALLOC(snd_seq_client_pool));
}
-/** call-seq: strerror(errno) -> string
+/** call-seq: snd_strerror(errno) -> string
Returns: the errorstring for the given system- or alsa-error.
*/
@@ -423,7 +440,7 @@ kernel_sleep_eintr_test(VALUE v_kernel)
if (r < 0)
{
if (errno == EINTR)
- rb_raise(alsaMidiError, "EINTR....");
+ RAISE_MIDI_ERROR_FMT0("EINTR....");
}
else
break;
@@ -433,122 +450,68 @@ kernel_sleep_eintr_test(VALUE v_kernel)
}
#endif
-extern "C" void
-Init_alsa_midi()
+static VALUE error_handler_block_proc = Qnil;
+
+static void
+my_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
{
-/** Document-class: RRTS
-
-This module is the namespace for the entire MIDI API.
-
-RRTS originally stood for Ruby RealTime Sequencer. But how RT can it be?
-Currently it contains the Alsa (Advanced Linux Sound Architecture) MIDI Driver
-plus supporter classes, in particular Sequencer.
-
-The following rules were used:
-- This is a literal implementation of the almost full alsa snd_seq API
-- functions have been made methods by using arg0 as +self+.
-- the +snd_seq_+ prefix was removed for methods, but not for constants.
-- special case seq_open for snd_seq_open, since just +open+ would conflict with
- Kernel#open.
-- the support classes have methods that do not require the Alsa constants anymore,
- however theses constants are still available in the RRTS::Driver namespace.
-- obvious default values for parameters are applied, whereas the original API is C,
- which has no defaults to begin with.
-- where values are often used as pairs (or even a c-struct) as in client+port=address
- I allow passing these as a tuple (array with elements at 0 and 1).
-- similarly, instances of the Driver classes can be used, or even the higher level
- classes (not always) where the original API expects the id or handle to be passed.
- This is always the case for queueids, where AlsaMidiQueue_i can be used, or for portids
- where AlsaMidiPort_i can be used.
-- methods starting with 'set_' (snd_seq_..._set) with a single (required) argument have been
- replaced by the equivalent setter in ruby (as 'port=')
-- +set+ methods with 0 or 2 or more arguments still remain
-- for methods starting with 'get_' this prefix has been removed as well.
-- names for getters that return a boolean are suffixed with '?'.
-- errors became exceptions, in particular AlsaMidiError and ENOSPC somewhere.
- Exceptions on this rule are methods used in finalizers, since exceptions in finalizers
- are really not funny. So close/free/clear or whatever return their original value.
- This also implies the errorcode returnvalue was abolished.
-- integer arguments and returnvalues that could be (or should be) clearly interpreted as booleans
- have been replaced by true booleans.
-- methods with a argumentaddress in C (Like 'int f(int *result)') are altered to return this parameter
- (so f -> [int, int]).
- In some cases this leads to returning a tuple.
-- methods that would always return nil (though the original may not) now return +self+.
-- in some cases, some parameters became meaningless.
-- normally in C, you would operate on the event object direct, using the structure definition.
- As in 'event.value = 23'
- This is no longer possible, but where names where unique within the union they became
- setters and getters. So event.value = 23 is still valid ruby.
- For ambiguous situations, the same approach is chosen, but the backend uses
- the type as set in the event.
- So:
- ev = ev_malloc
- ev.channel = 7
- is wrong as the type is not yet set and we must choose between ev.data.note.channel or
- ev.data.control.channel.
- But:
- ev = ev_malloc
- ev.note = 63
- is perfectly OK, since the +note+ selector is unambiguous (ev.data.note.note).
- The solution for the 'channel' case would then be:
- ev = ev_malloc
- ev.type = SND_SEQ_EVENT_NOTE
- ev.channel = 7
-- in some cases, alsa uses ambiguous names. For example, the macro +snd_seq_ev_set_source+ only sets
- the port, and not the client.
- This has been renamed to +source_port+, and +source_client+ is similar. Then the +source+
- setter and getter remain and they refer to the tuple client plus port.
- However for 'queue' this would not work, so
- +ev.queue+ refers to the queue on which the event was send or received while
- +ev.queue_queue+ refers to the queue as a subject of a queue control event
-- all other queue params have a 'queue_' prefix, including value.
- Example: +ev.data.queue.param.value+ should be replaced with +ev.queue_value+.
-- I have decided that nonblocking IO will cause the +EAGAIN+ systemerror to be raised, whenever this is
- appropriate. However the +output_event+ call can't always do this since it would cause events
- to be partly transmitted in some cases. So this call will always block.
- All functions that may block now (read: should) use +rb_thread_blocking_region+. Blocking mode
- should therefore really be on, as this is much easier, and I don't really see any use of
- nonblocking mode anymore.
- Here is a list of all blocking methods:
- - snd_seq_event_input
- - snd_seq_event_output
- - snd_seq_drain_output
-
-*IMPORTANT*: using this API as is, will not be the most efficient way to deal with
-alsa_midi.so. Please use the ruby classes and additional methods in this library.
-See alsa_midi++.cpp
-This yields in particular for the MidiEvent API since the only way to write or read
-a field is through a wrapped method. Even more, the C API has a lot of macros that
-are now implemented as ruby methods. Again, this is not efficient.
-However, it implies that existing programs can easily be ported, see for instance
-rrecordmidi.rb which is a 1 on 1 port of arecordmidi.c.
-Same for rplaymidi.rb
-
-The 'revents' method is rather vague and the examples do not use it. What is the timeout?
-Or isn't there any? Anyway, the result is made consistent with that of poll.
-
-===Some things about certain parameters
-
-====connections
-
-represented by RRTS::MidiPort or by a splat or tuple [clientid, portid]
-or by a string that uses the port name like 'MIDI UM-2'.
-
-====realtimes
-
-represented by a float which is simple the number of seconds, or by a tuple
-or splat [seconds, nanoseconds]
-
-Note that some methods also except times in ticks if the value is an integer.
-So 35 is that much ticks, and not that much seconds, that would be 35.0(!)
-
-=== ids
-
-In case a method accepts an id, it always accepts the representing instance also.
-So you can pass a RRTS::MidiQueue where a queueid is expected etc..
+ va_list arg;
+ char *buffer = 0;
+ try
+ {
+ va_start(arg, fmt);
+ if (vasprintf(&buffer, fmt, arg) < 0)
+ {
+ va_end(arg);
+ RAISE_MIDI_ERROR_FMT1("Internal problem, could not vasprintf... errno=%d", errno);
+ }
+ va_end(arg);
+ rb_funcall(error_handler_block_proc, rb_intern("call"), 5, rb_str_new2(file),
+ INT2NUM(line), rb_str_new2(function), INT2NUM(err), rb_str_new2(buffer));
+ }
+ catch (...)
+ {
+ free(buffer);
+ throw;
+ }
+ free(buffer);
+}
+
+/** call-seq: snd_lib_error_set_handler do |file, line, funcname, errcode, errtext| ... end
+I don't know when this is triggered but it seems a good idea to not use it.
+Or at least to raising AlsaMidiError from it.
+I assume it is called if snd_lib_error is called which occurs a lot in the overall Alsa code, but
+rarely in the sequencer part (12 calls to SNDERR in total).
+
+This function sets a new error handler, or (if handler is NULL) the default one which prints the error messages to stderr.
+
+Note that the parameters differ from the C version as fmt + args is already formatted into errtext.
+
+FIXME: it seems to work better if RAISE_MIDI_ERROR would be replaced with SNDERR, and that
+we would always set a handler to raise a ruby exception.
+However, does the alsa lib handle exceptions correctly?
*/
+static VALUE
+wrap_snd_lib_error_set_handler(VALUE v_driver)
+{
+ if (!rb_block_given_p())
+ {
+ snd_lib_error_set_handler(0);
+ error_handler_block_proc = Qnil;
+ }
+ else
+ {
+ if (error_handler_block_proc == Qnil) // first call
+ snd_lib_error_set_handler(my_error_handler);
+ error_handler_block_proc = rb_block_proc();
+ }
+}
+
+extern "C" void
+Init_alsa_midi()
+{
+ rb_global_variable(&error_handler_block_proc);
VALUE rrtsModule = rb_define_module("RRTS");
/** Document-class: RRTS::Driver
@@ -588,9 +551,14 @@ So you can pass a RRTS::MidiQueue where a queueid is expected etc..
rb_define_module_function(alsaDriver, "remove_events_malloc", RUBY_METHOD_FUNC(wrap_snd_seq_remove_events_malloc), 0);
rb_define_module_function(alsaDriver, "client_pool_malloc", RUBY_METHOD_FUNC(wrap_snd_seq_client_pool_malloc), 0);
rb_define_module_function(alsaDriver, "system_info_malloc", RUBY_METHOD_FUNC(wrap_snd_seq_system_info_malloc), 0);
+ rb_define_module_function(alsaDriver, "query_subscribe_malloc", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_malloc), 0);
rb_define_module_function(alsaDriver, "ev_malloc", RUBY_METHOD_FUNC(ev_malloc), 0);
rb_define_module_function(alsaDriver, "param2sym", RUBY_METHOD_FUNC(param2sym_v), 1);
+ rb_define_module_function(alsaDriver, "snd_strerror", RUBY_METHOD_FUNC(wrap_snd_strerror), 1);
rb_define_module_function(alsaDriver, "strerror", RUBY_METHOD_FUNC(wrap_snd_strerror), 1);
+ // I only made this because aconnect uses it, but in fact this is stupid. Just catch the exceptions I guess.
+ rb_define_module_function(alsaDriver, "snd_lib_error_set_handler", RUBY_METHOD_FUNC(wrap_snd_lib_error_set_handler), 0);
+// rb_define_module_function(alsaDriver, "snderr", RUBY_METHOD_FUNC(wrap_snderr), 1); DREADFULL SIN
rb_define_module_function(alsaDriver, "parse_address", RUBY_METHOD_FUNC(wrap_snd_seq_parse_address), 1);
#if defined(DEBUG)
@@ -610,5 +578,6 @@ So you can pass a RRTS::MidiQueue where a queueid is expected etc..
alsa_client_pool_init();
alsa_system_info_init();
alsa_midi_timer_init();
+ alsa_query_subscribe_init();
alsa_midi_plusplus_init();
}
View
24 lib/rrts/driver/alsa_midi.h
@@ -128,4 +128,28 @@ wrap_snd_seq_##strct##_copy_to(int argc, VALUE *argv, VALUE v_self) \
return retval; \
}
+#define FETCH_ADDRESSES() \
+VALUE v_clientid, v_portid; \
+rb_scan_args(argc, argv, "11", &v_clientid, &v_portid); \
+solve_address(v_clientid, v_portid)
+
+/* portid can be unset, and both can be an instance of client or port resp.
+*/
+static inline void solve_address(VALUE &v_clientid, VALUE &v_portid)
+{
+ if (NIL_P(v_portid))
+ {
+ // Now it may be that clientid responds to 'address'
+ RRTS_DEREF(v_clientid, address);
+ v_clientid = rb_check_array_type(v_clientid);
+ if (!RTEST(v_clientid)) RAISE_MIDI_ERROR_FMT0("API call error: address is not a tuple");
+ // and we can continue...
+ v_portid = rb_ary_entry(v_clientid, 1);
+ v_clientid = rb_ary_entry(v_clientid, 0);
+ }
+ RRTS_DEREF(v_clientid, client);
+ RRTS_DEREF(v_portid, port);
+}
+
+
#endif // _RRTS_ALSA_MIDI_H
View
4 lib/rrts/driver/alsa_midi_client.cpp
@@ -78,7 +78,7 @@ wrap_snd_seq_client_info_get_name(VALUE v_client_info)
/** call-seq: broadcast_filter?() -> bool
-Returns: the broadcast filter usage of a client_info container.
+Returns: the broadcast filter usage
*/
static VALUE
wrap_snd_seq_client_info_get_broadcast_filter(VALUE v_client_info)
@@ -90,7 +90,7 @@ wrap_snd_seq_client_info_get_broadcast_filter(VALUE v_client_info)
/** call-seq: error_bounce?() -> bool
-Returns: the error-bounce usage of a client_info container. But what is it?
+Returns: the error-bounce usage. But what is it?
*/
static VALUE
wrap_snd_seq_client_info_get_error_bounce(VALUE v_client_info)
View
23 lib/rrts/driver/alsa_midi_client.h
@@ -6,26 +6,3 @@ extern VALUE alsaClientInfoClass;
extern void alsa_midi_client_init();
-/* portid can be unset, and both can be an instance of client or port resp.
-*/
-static inline void solve_address(VALUE &v_clientid, VALUE &v_portid)
-{
- if (NIL_P(v_portid))
- {
- // Now it may be that clientid responds to 'address'
- RRTS_DEREF(v_clientid, address);
- v_clientid = rb_check_array_type(v_clientid);
- if (!RTEST(v_clientid)) RAISE_MIDI_ERROR_FMT0("API call error: address is not a tuple");
- // and we can continue...
- v_portid = rb_ary_entry(v_clientid, 1);
- v_clientid = rb_ary_entry(v_clientid, 0);
- }
- RRTS_DEREF(v_clientid, client);
- RRTS_DEREF(v_portid, port);
-}
-
-#define FETCH_ADDRESSES() \
-VALUE v_clientid, v_portid; \
-rb_scan_args(argc, argv, "11", &v_clientid, &v_portid); \
-solve_address(v_clientid, v_portid)
-
View
37 lib/rrts/driver/alsa_midi_event.cpp
@@ -995,8 +995,9 @@ alsaMidiEventClass_set_source_port(VALUE v_ev, VALUE v_portid)
/** call-seq: queue_skew() -> [skewvalue, base]
-Returns: the queue skew as a tuple value + base
-I have no idea what a queue skew is at this point. See Alsa docs (but they won't tell you)
+Returns: the queue skew as a tuple value + base.
+This is the relative queue speed (ie skewvalue.to_f / base.to_f).
+So by doubling the skewvalue the queue will play at double speed.
*/
static VALUE
alsaMidiEventClass_queue_skew(VALUE v_ev)
@@ -1008,7 +1009,9 @@ alsaMidiEventClass_queue_skew(VALUE v_ev)
/** call-seq: queue_skew = [value, base]
-You can also pass a splat.
+You can also pass a splat or a double. This basically tweaks the queuespeed.
+This can be convenient if you have a recording in realtime. To play it at
+a different speed, instead of tweaking all timestamps, the skew of the queue can be altered instead.
*/
static VALUE
alsaMidiEventClass_set_queue_skew(int argc, VALUE *argv, VALUE v_ev)
@@ -1024,8 +1027,20 @@ alsaMidiEventClass_set_queue_skew(int argc, VALUE *argv, VALUE v_ev)
}
snd_seq_event_t *ev;
Data_Get_Struct(v_ev, snd_seq_event_t, ev);
- ev->data.queue.param.skew.value = NUM2UINT(v_val);
- ev->data.queue.param.skew.base = NUM2UINT(v_base);
+ if (FIXNUM_P(v_val))
+ {
+ ev->data.queue.param.skew.value = NUM2UINT(v_val);
+ ev->data.queue.param.skew.base = NUM2UINT(v_base);
+ }
+ else
+ {
+ VALUE v_skewdbl = rb_check_float_type(v_val);
+ if (!RTEST(v_skewdbl))
+ RAISE_MIDI_ERROR_FMT0("API call error: bad skew format");
+ const double t = NUM2DBL(v_skewdbl);
+ ev->data.queue.param.skew.value = int(t * 0x10000);
+ ev->data.queue.param.skew.base = 0x10000;
+ }
return Qnil;
}
@@ -1405,7 +1420,9 @@ wrap_snd_seq_ev_set_broadcast(VALUE v_ev)
Specify that the event does not use a queue and is send immediately.
If neither a schedule is performed, and 'direct' is not set, then the event
-will be buffered and will only be send on a flush
+will be buffered and will only be send on a flush.
+
+This is not a setter since 'direct = false' cannot be done.
*/
static VALUE
wrap_snd_seq_ev_set_direct(VALUE v_ev)
@@ -1458,7 +1475,9 @@ wrap_snd_seq_ev_set_variable(VALUE v_ev, VALUE v_data)
/** call-seq: set_varusr(data) -> self
-set a varusr event's data, making it a VARUSR event
+set a varusr event's data, making it a VARUSR event.
+This is the same as a SYSEX event except that the data is not copied
+to kernelspace. Because of this it can only be sent in _direct_ mode!
*/
static VALUE
wrap_snd_seq_ev_set_varusr(VALUE v_ev, VALUE v_data)
@@ -1612,8 +1631,8 @@ the following extra type checking methods exist:
- fixed_type?
- variable_type?
- varusr_type?
-- abstime? , is the time absolute
-- reltime? , or relative
+- abstime? , is the time absolute (relative to starttime of q)
+- reltime? , or relative (relative to current time of q)
- direct? , bypass buffers when sending
- reserved? , stay off
- prior? , is it a high-priority event
View
5 lib/rrts/driver/alsa_midi_port.cpp
@@ -426,10 +426,9 @@ alsa_midi_port_init()
}
/** Document-class: RRTS::Driver::AlsaPortInfo_i
- This class represents a 'port' ie, an Alsa connection. Ports have an associated
+ This class represents a 'port' ie, the halve of an Alsa connection. Ports have an associated
client (like the sequencer itself).
- Ports cannot be directly used to read or write events from/to, you need RRTS::Driver::AlsaSequencer_i
- for that.
+ Ports cannot be directly used to read or write events from/to, you need to connect of them.
This class merely functions as a way to retrieve information about a certain port.
Example:
View
45 lib/rrts/driver/alsa_midi_queue.cpp
@@ -54,7 +54,8 @@ wrap_snd_seq_queue_info_set_flags(VALUE v_qi, VALUE v_flags)
/** call-seq: locked = bool
I believe that queues are created locked, but you may lock or unlock them
-with this method. But what does it do?? The Alsa documentation says nothing...
+with this method. This means that other clients can not alter its parameters,
+like the timer
*/
static VALUE
wrap_snd_seq_queue_info_set_locked(VALUE v_qi, VALUE v_locked)
@@ -187,8 +188,8 @@ wrap_snd_seq_queue_tempo_get_queue(VALUE v_tempo)
/** call-seq: skew() -> int
-Get the timer skew value, whatever it is. Probably you can program some fixed delay
-on the queue ?
+Get the timer skew value. The skew_value/skew_base form the relative speed of the
+queue and this ratio is normally 1
See RRTS::Driver::AlsaQueueTempo_i#skew_base
*/
static VALUE
@@ -201,7 +202,7 @@ wrap_snd_seq_queue_tempo_get_skew(VALUE v_tempo)
/** call-seq: skew_base() -> int
-Get the timer skew base value of a queue_status container.
+Get the timer skew base value. By default this is 0x10000
See RRTS::Driver::AlsaQueueTempo_i#skew.
*/
static VALUE
@@ -240,17 +241,31 @@ wrap_snd_seq_queue_tempo_set_ppq(VALUE v_tempo, VALUE v_ppq)
}
/** call-seq: skew = value
+Change the skew value if value is an int. If it is a double then both
+skew value and skew base are set so that skew/base == value.
*/
static VALUE
wrap_snd_seq_queue_tempo_set_skew(VALUE v_tempo, VALUE v_skew)
{
snd_seq_queue_tempo_t *tempo;
Data_Get_Struct(v_tempo, snd_seq_queue_tempo_t, tempo);
- snd_seq_queue_tempo_set_skew(tempo, NUM2UINT(v_skew));
+
+ VALUE v_dbl = rb_check_float_type(v_skew);
+ if (RTEST(v_dbl))
+ {
+ const double t = NUM2DBL(v_dbl);
+ snd_seq_queue_tempo_set_skew(tempo, int(t * 0x10000));
+ snd_seq_queue_tempo_set_skew_base(tempo, 0x10000);
+ }
+ else
+ snd_seq_queue_tempo_set_skew(tempo, NUM2UINT(v_skew));
return Qnil;
}
/** call-seq: skew_base = value
+
+Normally this is set to 0x10000. The ratio skew_value/skew_base is the relative speed of the
+queue.
*/
static VALUE
wrap_snd_seq_queue_tempo_set_skew_base(VALUE v_tempo, VALUE v_skew)
@@ -333,6 +348,7 @@ wrap_snd_seq_queue_status_get_real_time(VALUE v_status)
/** call-seq: status() -> int
Returns: something. 'status bits' says the Alsa doc. But which?
+According to Eugene it is != 0 for a running queue.
*/
static VALUE
wrap_snd_seq_queue_status_get_status(VALUE v_status)
@@ -342,6 +358,18 @@ wrap_snd_seq_queue_status_get_status(VALUE v_status)
return UINT2NUM(snd_seq_queue_status_get_status(status));
}
+/** call-seq: running?() -> bool
+
+Returns: status() != 0.
+*/
+static VALUE
+wrap_snd_seq_queue_status_get_status_ex(VALUE v_status)
+{
+ snd_seq_queue_status_t *status;
+ Data_Get_Struct(v_status, snd_seq_queue_status_t, status);
+ return INT2BOOL((int)snd_seq_queue_status_get_status(status));
+}
+
void
alsa_midi_queue_init()
{
@@ -362,18 +390,22 @@ alsa_midi_queue_init()
A queue can operate as a recording or playback device. You can use queueevents to operate on the
queue to start, pause and continue it, or you can set the position (time) to a specific value
causing events to be skipped or replayed.
+
+ To get a queueinfo use RRTS::Driver::AlsaSequencer_i#queue_info
*/
alsaQueueInfoClass = rb_define_class_under(alsaDriver, "AlsaQueueInfo_i", rb_cObject);
/** Document-class: RRTS::Driver::AlsaQueueTempo_i
- This wrapper is used for setting and retrieving tempo information
+ This wrapper is used for setting and retrieving tempo information.
+ To get the tempo use RRTS::Driver::AlsaSequencer_i#queue_tempo
*/
alsaQueueTempoClass = rb_define_class_under(alsaDriver, "AlsaQueueTempo_i", rb_cObject);
/** Document-class: RRTS::Driver::AlsaQueueStatus_i
This wrapper is used for retrieving the current 'time' of the queue.
+ To get the tempo use RRTS::Driver::AlsaSequencer_i#queue_status
*/
alsaQueueStatusClass = rb_define_class_under(alsaDriver, "AlsaQueueStatus_i", rb_cObject);
@@ -406,5 +438,6 @@ alsa_midi_queue_init()
rb_define_method(alsaQueueStatusClass, "tick_time", RUBY_METHOD_FUNC(wrap_snd_seq_queue_status_get_tick_time), 0);
rb_define_method(alsaQueueStatusClass, "real_time", RUBY_METHOD_FUNC(wrap_snd_seq_queue_status_get_real_time), 0);
rb_define_method(alsaQueueStatusClass, "status", RUBY_METHOD_FUNC(wrap_snd_seq_queue_status_get_status), 0);
+ rb_define_method(alsaQueueStatusClass, "running?", RUBY_METHOD_FUNC(wrap_snd_seq_queue_status_get_status_ex), 0);
rb_define_method(alsaQueueStatusClass, "copy_to", RUBY_METHOD_FUNC(wrap_snd_seq_queue_status_copy_to), -1);
}
View
13 lib/rrts/driver/alsa_port_subscription.cpp
@@ -135,11 +135,6 @@ wrap_snd_seq_port_subscribe_get_time_update(VALUE v_port_subs)
return INT2BOOL(snd_seq_port_subscribe_get_time_update(port_subs));
}
-#define FETCH_ADDRESSES() \
-VALUE v_clientid, v_portid; \
-rb_scan_args(argc, argv, "11", &v_clientid, &v_portid); \
-solve_address(v_clientid, v_portid)
-
/** call-seq: dest = addressspecification
Set destination address of a port_subscribe container.
@@ -261,11 +256,17 @@ port_subscription_init()
}
/** Document-class: RRTS::Driver::AlsaPortSubscription_i
- Ones you subscribe for an Alsa connection between two ports you receive events from that
+ Once you subscribe for an Alsa connection between two ports you receive events from that
connection, and you can write events to it as well.
A queue can be associated with the connection and timestamping can be switched on.
See: http://www.alsa-project.org/~tiwai/alsa-subs.html
+
+ Note that AlsaSequencer_i#connect_to and AlsaSequencer_i#connect_from also create subscriptions!
+ However they cannot be exclusive, and they do not convert timestamps if the two clients use
+ different timing systems.
+
+ Currently this class has no higher level representation in RRTS.
*/
alsaPortSubscriptionClass = rb_define_class_under(alsaDriver, "AlsaPortSubscription_i", rb_cObject);
rb_define_method(alsaPortSubscriptionClass, "dest", RUBY_METHOD_FUNC(wrap_snd_seq_port_subscribe_get_dest), 0);
View
363 lib/rrts/driver/alsa_query_subscribe.cpp
@@ -0,0 +1,363 @@
+
+#pragma implementation
+#include "alsa_query_subscribe.h"
+#include "alsa_midi.h"
+#include <alsa/asoundlib.h>
+#include <ruby/ruby.h>
+#include <ruby/dl.h>
+
+VALUE alsaQuerySubscribeClass;
+
+/** Document-method: RRTS::Driver::AlsaQuerySubscribe_i#copy_to
+call-seq: copy_to([other=nil]) -> clone
+
+Parameters:
+[other] if given copy +self+ to it, otherwise create a copy and return it. This copy need
+ not be freed.
+*/
+ALSA_MIDI_COPY_TO_TEMPLATE(query_subscribe, QuerySubscribe)
+
+/** call-seq: addr -> [clientid, portid]
+
+Get the address of subscriber. This is the 'external' end of a connection, from
+the initiating clients point of view.
+
+If I say:
+
+ ruby bin/rconnect.rb 20:1 14:0
+
+to connect UM-out (sender 20:1) to MIDI-through-in (dest 14:0) then the connection is listed in the input list:
+
+ client 14: 'Midi Through' [type=kernel]
+ 0 'Midi Through Port-0'
+ Connected From: 20:1
+ client 20: 'UM-2' [type=kernel]
+ 1 'UM-2 MIDI 2 '
+ Connecting To: 14:0
+
+Returns:
+ subscriber's address pointer, that is the sender for 'connect-to' and the destination for 'connect-from'
+ querytypes.
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#addr
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_addr(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ const snd_seq_addr_t * const addr = snd_seq_query_subscribe_get_addr(info);
+ return rb_ary_new3(2, INT2NUM(addr->client), INT2NUM(addr->port));
+}
+
+/** call-seq: client -> int
+
+Get the client id of a query_subscribe container.
+
+Returns:
+ client id
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#client=
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_client(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ return INT2NUM(snd_seq_query_subscribe_get_client(info));
+}
+
+/** call-seq: exclusive? -> bool
+
+Get the exclusive mode of a query_subscribe container.
+
+Returns:
+ true if exclusive mode
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers()
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_exclusive(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ return INT2BOOL(snd_seq_query_subscribe_get_exclusive(info));
+}
+
+/** call-seq: index -> int
+
+Returns:
+ subscriber's index
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#index=
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_index(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ return INT2NUM(snd_seq_query_subscribe_get_index(info));
+}
+
+/** call-seq: num_subs -> int
+
+Returns:
+ the number of subscriptions
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers()
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_num_subs(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ return INT2NUM(snd_seq_query_subscribe_get_num_subs(info));
+}
+
+/** call-seq: port -> int
+
+Returns:
+ the port id
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#port=
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_port(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ return INT2NUM(snd_seq_query_subscribe_get_port(info));
+}
+
+/** call-seq: queue -> int
+
+Returns:
+ the queue id
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers()
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_queue(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ return INT2NUM(snd_seq_query_subscribe_get_queue(info));
+}
+
+/** call-seq: root -> [clientid, portid]
+
+Get the root address
+
+Returns:
+ subscriber's root address pointer, for the READ (or 'connect-to') query type, +root+ is the sender,
+ otherwise it is the destination. So we have:
+
+ ROOT connect to ADDR, SENDER -> DEST
+
+ or
+
+ ROOT connect from ADDR DEST <- SENDER
+
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers(), RRTS::Driver::AlsaQuerySubscribe_i#root= and
+ RRTS::Driver::AlsaQuerySubscribe_i#addr
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_root(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ const snd_seq_addr_t * const addr = snd_seq_query_subscribe_get_root(info);
+ return rb_ary_new3(2, INT2NUM(addr->client), INT2NUM(addr->port));
+}
+
+/** call-seq: time_real? -> bool
+
+Get the realtime update mode of a query_subscribe container.
+
+Returns:
+ true if realtime update mode
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers()
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_time_real(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ return INT2BOOL(snd_seq_query_subscribe_get_time_real(info));
+}
+
+/** call-seq: time_update? -> bool
+
+Get the time-update mode
+
+Returns:
+ true if timestamps are updated
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers()
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_time_update(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ return INT2BOOL(snd_seq_query_subscribe_get_time_update(info));
+}
+
+/** call-seq: type -> int
+snd_seq_query_subs_type_t snd_seq_query_subscribe_get_type ( const snd_seq_query_subscribe_t * info )
+
+Get the query type
+
+Parameters:
+ info query_subscribe container
+
+Returns:
+ query type, either SND_SEQ_QUERY_SUBS_READ or SND_SEQ_QUERY_SUBS_WRITE
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#type=
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_get_type(VALUE v_info)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ return INT2NUM(snd_seq_query_subscribe_get_type(info));
+}
+
+/** call-seq: client = id
+
+Set the client id, to initiate a query.
+
+Parameters:
+[client] client id
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#client
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_set_client(VALUE v_info, VALUE v_id)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ snd_seq_query_subscribe_set_client(info, NUM2INT(v_id));
+ return Qnil;
+}
+
+/** call-seq: index = id
+
+Set the index, to initiate a query.
+
+Parameters:
+[index] index to be queried
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#index
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_set_index(VALUE v_info, VALUE v_id)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ snd_seq_query_subscribe_set_index(info, NUM2INT(v_id));
+ return Qnil;
+}
+
+/** call-seq: port = id
+
+Set the port id, to initiate a query.
+
+Parameters:
+[port] port id to be queried
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#port
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_set_port(VALUE v_info, VALUE v_id)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ snd_seq_query_subscribe_set_port(info, NUM2INT(v_id));
+ return Qnil;
+}
+
+/** call-seq: root = address_specification
+
+Set the client and port id, to initiate a query.
+
+Parameters:
+[address_specification] a single MidiPort, or a combination op clientid and portid
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#root
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_set_root(int argc, VALUE *argv, VALUE v_info)
+{
+ FETCH_ADDRESSES();
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ const snd_seq_addr_t root = { NUM2INT(v_clientid), NUM2INT(v_portid) };
+ snd_seq_query_subscribe_set_root(info, &root);
+ return Qnil;
+}
+
+/** call-seq: type = int
+
+Set the type of query
+
+Parameters:
+[type] either SND_SEQ_QUERY_SUBS_READ or else SND_SEQ_QUERY_SUBS_WRITE
+
+See also:
+ RRTS::Driver::AlsaSequencer_i#query_port_subscribers() and RRTS::Driver::AlsaQuerySubscribe_i#type
+*/
+static VALUE
+wrap_snd_seq_query_subscribe_set_type(VALUE v_info, VALUE v_id)
+{
+ snd_seq_query_subscribe_t *info;
+ Data_Get_Struct(v_info, snd_seq_query_subscribe_t, info);
+ const int tp = NUM2INT(v_id);
+ if (tp != SND_SEQ_QUERY_SUBS_READ && tp != SND_SEQ_QUERY_SUBS_WRITE)
+ RAISE_MIDI_ERROR("bad type %d", tp);
+ snd_seq_query_subscribe_set_type(info, (snd_seq_query_subs_type_t)tp);
+ return Qnil;
+}
+
+void
+alsa_query_subscribe_init()
+{
+ WRAP_CONSTANT(SND_SEQ_QUERY_SUBS_READ);
+ WRAP_CONSTANT(SND_SEQ_QUERY_SUBS_WRITE);
+ alsaQuerySubscribeClass = rb_define_class_under(alsaDriver, "AlsaQuerySubscribe_i", rb_cObject);
+ rb_define_method(alsaQuerySubscribeClass, "copy_to", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_copy_to), -1);
+ rb_define_method(alsaQuerySubscribeClass, "client", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_client), 0);
+ rb_define_method(alsaQuerySubscribeClass, "port", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_port), 0);
+ rb_define_method(alsaQuerySubscribeClass, "root", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_root), 0);
+ rb_define_method(alsaQuerySubscribeClass, "type", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_type), 0);
+ rb_define_method(alsaQuerySubscribeClass, "index", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_index), 0);
+ rb_define_method(alsaQuerySubscribeClass, "num_subs", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_num_subs), 0);
+ rb_define_method(alsaQuerySubscribeClass, "addr", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_addr), 0);
+ rb_define_method(alsaQuerySubscribeClass, "queue", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_queue), 0);
+ rb_define_method(alsaQuerySubscribeClass, "exclusive?", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_exclusive), 0);
+ rb_define_method(alsaQuerySubscribeClass, "time_update?", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_time_update), 0);
+ rb_define_method(alsaQuerySubscribeClass, "time_real?", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_get_time_real), 0);
+ rb_define_method(alsaQuerySubscribeClass, "client=", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_set_client), 1);
+ rb_define_method(alsaQuerySubscribeClass, "port=", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_set_port), 1);
+ rb_define_method(alsaQuerySubscribeClass, "root=", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_set_root), -1);
+ rb_define_method(alsaQuerySubscribeClass, "type=", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_set_type), 1);
+ rb_define_method(alsaQuerySubscribeClass, "index=", RUBY_METHOD_FUNC(wrap_snd_seq_query_subscribe_set_index), 1);
+}
View
8 lib/rrts/driver/alsa_query_subscribe.h
@@ -0,0 +1,8 @@
+
+#include <ruby.h>
+
+#pragma interface
+
+extern VALUE alsaQuerySubscribeClass;
+
+extern void alsa_query_subscribe_init();
View
140 lib/rrts/driver/alsa_seq.cpp
@@ -583,7 +583,12 @@ static inline uint
PARAM_IS_MSB_LSB_PAIR(uint param)
{
trace("PARAM_IS_MSB_LSB_PAIR")
+ // Compiler will complain, since MIDI_CTL_MSB_BANK is actually 0.
+#if MIDI_CTL_MSB_BANK == 0
+ if (param <= MIDI_CTL_MSB_GENERAL_PURPOSE4)
+#else
if (param >= MIDI_CTL_MSB_BANK && param <= MIDI_CTL_MSB_GENERAL_PURPOSE4)
+#endif
return param + (MIDI_CTL_LSB_BANK - MIDI_CTL_MSB_BANK);
switch (param)
{
@@ -633,7 +638,8 @@ WRITE_TIME_IN_CHANNEL_i(VALUE v_time, snd_seq_event_t &ev, bool have_sender_queu
// depending on ev.flags we use tick or tv_sec/tv_nsec tuple (array)
#define WRITE_TIME_IN_CHANNEL(t, e) WRITE_TIME_IN_CHANNEL_i(t, e, have_sender_queue)
-static uint decode_a_note(const char *pat)
+static uint
+decode_a_note(const char *pat)
{
int base; // 0..11 where C == 0 , C#==Db == 1 upto B=11
//int oct; // 0..9. 12*oct+base is the midi notenr. Max 12*9+11 = 119
@@ -668,8 +674,16 @@ static uint decode_a_note(const char *pat)
return (pat[i] - '0') * 12 + base;
}
+static VALUE
+wrap_decode_a_note(VALUE v_driver, VALUE v_str)
+{
+ const char * const str = StringValueCStr(v_str);
+ return UINT2NUM(decode_a_note(str));
+}
+
// helper. To be called if blocking is simply required
-static int help_snd_seq_drain_output(snd_seq_t *seq)
+static int
+help_snd_seq_drain_output(snd_seq_t *seq)
{
for (;;)
{
@@ -1042,7 +1056,7 @@ wrap_snd_seq_query_next_client(VALUE v_seq, VALUE v_client_info)
const int r = snd_seq_query_next_client(seq, client_info);
// fprintf(stderr, "snd_seq_query_next_client -> %d\n", r);
if (r == -ENOENT) return Qfalse;
- if (r < 0) rb_raise(alsaMidiError, "%s", snd_strerror(r));
+ if (r < 0) RAISE_MIDI_ERROR("querying client", r);
return Qtrue;
}
@@ -1073,12 +1087,15 @@ wrap_snd_seq_query_next_port(VALUE v_seq, VALUE v_port_info)
/** call-seq: alloc_named_queue(name) -> int
allocate a queue with the specified name.
-According to aplaymidi.c this queue is _locked_ (which is just fine, but what does it mean)
+This queue is _locked_ by default, which means other clients can not use
+it. You can make it public though.
Parameters:
[name] the name of the new queue
Returns: the queue id (zero or positive) on success, throws RRTS::AlsaMidiError otherwise
+The queue should be freed using RRTS::Driver::AlsaSequencer_i#free_queue.
+
*/
static VALUE
wrap_snd_seq_alloc_named_queue(VALUE v_seq, VALUE v_name)
@@ -1090,7 +1107,7 @@ wrap_snd_seq_alloc_named_queue(VALUE v_seq, VALUE v_name)
return INT2NUM(r);
}
-/** call-seq: subscribe_port(subinfo) -> subinfo
+/** call-seq: subscribe_port(subinfo) -> AlsaPortSubscribe_i
Parameters:
[subinfo] subscription information
@@ -1230,7 +1247,7 @@ wrap_snd_seq_parse_address(VALUE v_seq, VALUE v_arg)
snd_seq_t *seq;
Data_Get_Struct(v_seq, snd_seq_t, seq);
snd_seq_addr_t ret;
- const char *const arg = StringValuePtr(v_arg);
+ const char *const arg = StringValueCStr(v_arg);
const int r = snd_seq_parse_address(seq, &ret, arg);
if (r < 0) RAISE_MIDI_ERROR_FMT2("Invalid port '%s' - %s", arg, snd_strerror(r));
return rb_ary_new3(2, INT2NUM(ret.client), INT2NUM(ret.port));
@@ -1580,11 +1597,13 @@ wrap_snd_seq_query_named_queue(VALUE v_seq, VALUE v_name)
/** call-seq: start_queue(queue) -> self
-start the specified queue
+start the specified queue. Note that this just sends an event to the output. You still
+need to flush the buffer before it takes effect.
Parameters:
[queue] queueid or RRTS::MidiQueue to start
[ev] optional event record (see snd_seq_control_queue) CURRENTLY NOT SUPPORTED!
+ This event can be used to schedule the start event.
See also RRTS::MidiQueue#start
*/
@@ -1618,12 +1637,12 @@ wrap_snd_seq_stop_queue(VALUE v_seq, VALUE v_qid)
return Qnil;
}
-/** call-seq: queue_usage(queue) -> bool
+/** call-seq: queue_usage?(queue) -> bool
Parameters:
[queue] queueid or RRTS::MidiQueue
-Returns: true if client is allowed to access the queue, false if not allowed,
+Returns: true if other clients are allowed to access the queue, false if not allowed,
on errors it raises RRTS::AlsaMidiError
*/
static VALUE
@@ -2184,7 +2203,98 @@ wrap_snd_seq_system_info(int argc, VALUE *argv, VALUE v_seq)
return v_info;
}