stringyfi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 796dc3f1361b70dbb6c86d6f7ef77639796d12e5
4
+ data.tar.gz: f7ce1efbc6ead2e92c99b37f629540cc69d6f5db
5
+ SHA512:
6
+ metadata.gz: 92a250942f88d6ee07ff351ce5a4fd7a2998a675f8773b1782a913cc94fc5bde540a98fcdbf2bc40b0d5d6baba3a0f1c142c4708eb776d6c3dc666f067a68c7e
7
+ data.tar.gz: 07ea32854390d1bcb07065ef3f5a44609399d56f1dbc04df387bd44044f66c5451ad9573c6965440b954653dfb353039f2889671a148cb744fc6a22d0f3485e4
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ stringyfi
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.1
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ # These are specific configuration settings required for travis-ci
2
+ # see http://travis-ci.org/tardate/stringyfi
3
+ language: ruby
4
+ rvm:
5
+ - 2.4.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in amuse.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,18 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ # Note: The cmd option is now required due to the increasing number of ways
5
+ # rspec may be run, below are examples of the most common uses.
6
+ # * bundler: 'bundle exec rspec'
7
+ # * bundler binstubs: 'bin/rspec'
8
+ # * spring: 'bin/rsspec' (This will use spring if running and you have
9
+ # installed the spring binstubs per the docs)
10
+ # * zeus: 'zeus rspec' (requires the server to be started separetly)
11
+ # * 'just' rspec: 'rspec'
12
+ guard :rspec, cmd: 'bundle exec rspec' do
13
+ watch(%r{^spec/.+_spec\.rb$})
14
+ watch(%r{^lib/stringyfi/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
15
+ watch('spec/spec_helper.rb') { "spec" }
16
+
17
+ end
18
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2017 Paul Gallagher
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # StringyFi
2
+
3
+ Convert MusicXML to PIC assembler for running on the Boldport Club Stringy.
4
+
5
+ [![Build Status](https://travis-ci.org/tardate/stringyfi.svg?branch=master)](https://travis-ci.org/tardate/stringyfi)
6
+
7
+ ## About the Stringy
8
+
9
+ The [Stringy](https://www.boldport.com/products/stringy/) is an open source hardware project
10
+ from the wonderful [Boldport Club](http://www.boldport.club/).
11
+
12
+ The Stringy was Project #14 from June 2017, and is a remix of
13
+ [MadLab's 'Funky guitar'](http://www.madlab.org/kits/guitar.html).
14
+
15
+ ## About StringyFi
16
+
17
+ StringyFi is a simple gem that converts MusicXML source files to
18
+ a PIC assembler source format that can be compiled and programmed to the Stringy.
19
+
20
+ See [LEAP#349 DemoBurner](https://github.com/tardate/LittleArduinoProjects/tree/master/BoldportClub/stringy/DemoBurner)
21
+ for a complete example of this in practice.
22
+
23
+ StringyFi has some serious limitations, some of which are in its implementation of MusicXML parsing,
24
+ some are fundamental limitations of the Stringy. I have found that most scores need tweaking
25
+ to be reproduced acceptably on the Stringy, and some are just too complex (without re-writing the Stringy firmware en-masse).
26
+ Some key points to note:
27
+
28
+ * The String uses ony 2-bit (4 levels) of note duration, so the conversion squeezes the score into 4 note durations as best as possible
29
+ * many notation features ignored: slides, ties etc
30
+
31
+ ## Installation
32
+
33
+ Add this line to your application's Gemfile:
34
+
35
+ gem 'stringyfi'
36
+
37
+ And then execute:
38
+
39
+ $ bundle
40
+
41
+ Or install it yourself as:
42
+
43
+ $ gem install stringyfi
44
+
45
+ ## Usage
46
+
47
+ The StringyFi executable accepts a file path to the MusicXML source to convert,
48
+ and emits the assembler source on STDOUT (so it can be redirected as appropriate).
49
+
50
+ For example:
51
+
52
+ $ stringyfi ./spec/fixtures/music_xml/chromatic.xml > ../CustomDemo.X/demo.tun
53
+
54
+ The
55
+ [spec/fixtures](./spec/fixtures/music_xml)
56
+ contains a few examples that are also used in tests.
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create a new Pull Request
65
+ (6. Join Boldport Club!)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ desc "Open an irb session preloaded with this library"
9
+ task :console do
10
+ sh "irb -rubygems -I lib -r stringyfi.rb"
11
+ end
12
+
data/bin/stringyfi ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'stringyfi'
5
+ StringyFi::Shell.new(ARGV).convert!
data/lib/stringyfi.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'stringyfi/version'
2
+ require 'stringyfi/shell'
3
+ require 'stringyfi/note'
4
+ require 'stringyfi/measures'
5
+ require 'stringyfi/converter'
@@ -0,0 +1,164 @@
1
+ require 'nokogiri'
2
+
3
+ class StringyFi::Converter
4
+
5
+ INITIAL_OCTAVE = 1 # setup by the stringy firmware
6
+ OCTAVE_OFFSET = 3 # how many octaves to offset the source
7
+
8
+ attr_accessor :filename, :xml_doc
9
+
10
+ def initialize(filename)
11
+ self.filename = filename
12
+ end
13
+
14
+ def convert!
15
+ $stderr.puts "converting #{filename}.."
16
+ shortest_fractional_duration = measures.shortest_fractional_duration
17
+ $stderr.puts " shortest_fractional_duration: #{shortest_fractional_duration}"
18
+ $stderr.puts " octave_range: #{measures.octave_range.inspect}"
19
+
20
+ puts score_preamble
21
+ puts score_body(shortest_fractional_duration)
22
+ puts score_coda
23
+
24
+ $stderr.puts ".. done."
25
+ end
26
+
27
+ def score_preamble
28
+ <<-EOS
29
+ ;**************************************************************************
30
+ ;** Title: #{title}
31
+ ;** Tempo: #{tempo}
32
+ ;** Encoded: #{encoding_date} with #{encoding_software}
33
+ ;** Stringyfied: #{Time.now}
34
+ ;**************************************************************************
35
+
36
+ \ttstart DemoTune
37
+ EOS
38
+ end
39
+
40
+ def score_coda
41
+ <<-EOS
42
+ \ttrest 8
43
+ \ttstop
44
+ EOS
45
+ end
46
+
47
+ def score_body(shortest_fractional_duration)
48
+ lines = []
49
+ current_octave = INITIAL_OCTAVE + OCTAVE_OFFSET
50
+ measures.each_with_index do |measure, measure_index|
51
+ lines << "\t; measure #{measure_index+1}"
52
+ measure.each do |note|
53
+ unless note.rest?
54
+ if note.octave != current_octave
55
+ delta = note.octave - current_octave
56
+ sign = delta > 0 ? "+" : "-"
57
+ (delta.abs).times do
58
+ lines << "\ttoctave #{sign}1"
59
+ end
60
+ current_octave = note.octave
61
+ end
62
+ end
63
+ short_repeats, medium_repeats, long_repeats, very_long_repeats = note.stringy_durations(shortest_fractional_duration)
64
+ if note.rest?
65
+ (short_repeats).times { lines << "\ttrest 1" }
66
+ (medium_repeats).times { lines << "\ttrest 2" }
67
+ (long_repeats).times { lines << "\ttrest 3" }
68
+ (very_long_repeats).times { lines << "\ttrest 4" }
69
+ else
70
+ (short_repeats).times { lines << "\ttnote #{note.to_stringy(current_octave)},1,0 ; #{note.to_note_id}" }
71
+ (medium_repeats).times { lines << "\ttnote #{note.to_stringy(current_octave)},2,0 ; #{note.to_note_id}" }
72
+ (long_repeats).times { lines << "\ttnote #{note.to_stringy(current_octave)},3,0 ; #{note.to_note_id}" }
73
+ (very_long_repeats).times { lines << "\ttnote #{note.to_stringy(current_octave)},4,0 ; #{note.to_note_id}" }
74
+ end
75
+ end
76
+ end
77
+ lines
78
+ end
79
+
80
+ def title
81
+ xml_doc.xpath('//work/work-title').text
82
+ end
83
+
84
+ # Simplified - only supports one tempo for the piece
85
+ # assumed for quarter note
86
+ def tempo
87
+ @tempo ||= xml_doc.xpath('//measure/direction/sound/@tempo').to_s.to_i
88
+ end
89
+
90
+ def encoding_date
91
+ xml_doc.xpath('//identification/encoding/encoding-date').text
92
+ end
93
+
94
+ def encoding_software
95
+ xml_doc.xpath('//identification/encoding/software').text
96
+ end
97
+
98
+ def identification
99
+ {
100
+ title: title,
101
+ encoding: {
102
+ date: encoding_date,
103
+ software: encoding_software
104
+ }
105
+ }
106
+ end
107
+ def part_list
108
+ xml_doc.xpath('//part-list/score-part').each_with_object([]) do |part,memo|
109
+ h = {}
110
+ h[:id] = part.attr('id')
111
+ memo << h
112
+ end
113
+ end
114
+
115
+ def parts
116
+ xml_doc.xpath('//part')
117
+ end
118
+
119
+ # Returns measures for the piece.
120
+ # only converts one part (for now)
121
+ # only includes staff 1
122
+ def measures
123
+ @measures ||= begin
124
+ measures = StringyFi::Measures.new
125
+ part = parts.first
126
+ part.xpath('measure').each_with_index do |part_measure, m|
127
+ measures[m] ||= []
128
+ part_measure.xpath('note').each_with_object(measures[m]) do |note, memo|
129
+ next unless note.xpath("staff").text == "1"
130
+ next unless note.xpath("voice").text == "1"
131
+ pitch = note.xpath("pitch")
132
+ duration = note.xpath("duration").text.to_i
133
+ actual_notes = note.xpath("actual-notes").text.to_i
134
+ normal_notes = note.xpath("normal-notes").text.to_i
135
+ if actual_notes > 0 and normal_notes > 0
136
+ duration = duration * 1.0 * normal_notes / actual_notes
137
+ end
138
+ duration_type = note.xpath("type").text
139
+ memo << StringyFi::Note.new(
140
+ pitch.xpath('step').text,
141
+ pitch.xpath('octave').text,
142
+ pitch.xpath('alter').text,
143
+ duration,
144
+ duration_type
145
+ )
146
+ end
147
+ end
148
+ measures
149
+ end
150
+ end
151
+
152
+ def measure(measure_id)
153
+ measures[measure_id]
154
+ end
155
+
156
+ def xml_doc
157
+ @xml_doc ||= Nokogiri::XML(io_stream)
158
+ end
159
+
160
+ def io_stream
161
+ File.open(filename).read
162
+ end
163
+
164
+ end
@@ -0,0 +1,27 @@
1
+ class StringyFi::Measures < Array
2
+
3
+ def shortest_fractional_duration
4
+ result = 1
5
+ each do |measure|
6
+ measure.each do |note|
7
+ result = note.fractional_duration if note.fractional_duration < result
8
+ end
9
+ end
10
+ result
11
+ end
12
+
13
+ def octave_range
14
+ lo = hi = nil
15
+ each do |measure|
16
+ measure.each do |note|
17
+ next if note.rest?
18
+ lo ||= note.octave
19
+ hi ||= note.octave
20
+ lo = note.octave if note.octave < lo
21
+ hi = note.octave if note.octave > hi
22
+ end
23
+ end
24
+ [lo, hi]
25
+ end
26
+
27
+ end
@@ -0,0 +1,97 @@
1
+ class StringyFi::Note
2
+
3
+ attr_accessor :name, :octave, :alter
4
+ attr_accessor :tempo
5
+ attr_accessor :fractional_duration
6
+
7
+ def initialize(name, octave=4, alter=nil, duration=1, duration_type="quarter")
8
+ self.name = "#{name}".upcase
9
+ self.octave = "#{octave}".to_i
10
+ self.alter = alter.to_i rescue 0
11
+ self.fractional_duration = calculate_fractional_duration(duration, duration_type)
12
+ end
13
+
14
+ def rest?
15
+ name == ""
16
+ end
17
+
18
+ def calculate_fractional_duration(duration, duration_type)
19
+ divisor = {
20
+ "half" => 2.0,
21
+ "quarter" => 4.0,
22
+ "eighth" => 8.0,
23
+ "16th" => 16.0,
24
+ "32nd" => 32.0
25
+ }[duration_type] || 1.0
26
+ duration/divisor
27
+ end
28
+
29
+ # retrun [short, medium, long, very_long] repeats for the note
30
+ # TODO: scale medium, long, very_long durations correctly
31
+ def stringy_durations(shortest_fractional_duration)
32
+ time_units = (fractional_duration / shortest_fractional_duration).to_i
33
+ case
34
+ when time_units >= 8
35
+ [0, 0, 0, 1]
36
+ when time_units >= 4
37
+ [0, 0, 1, 0]
38
+ when time_units >= 2
39
+ [0, 1, 0, 0]
40
+ else
41
+ [1, 0, 0, 0]
42
+ end
43
+ end
44
+
45
+ def to_stringy(current_octave)
46
+ relative_octave = octave - current_octave + 1
47
+ case alter
48
+ when 1
49
+ "#{name}#{relative_octave}S"
50
+ when -1
51
+ sharpy_name = {
52
+ 'B' => 'A',
53
+ 'E' => 'D',
54
+ 'A' => 'G',
55
+ 'D' => 'C',
56
+ 'G' => 'F',
57
+ }[name]
58
+ "#{sharpy_name}#{relative_octave}S"
59
+ else
60
+ "#{name}#{relative_octave}"
61
+ end
62
+ end
63
+
64
+ # Returns the note name, regardless of octave
65
+ def to_note_name
66
+ case alter
67
+ when 1
68
+ "#{name}#"
69
+ when -1
70
+ "#{name}b"
71
+ else
72
+ name
73
+ end
74
+ end
75
+
76
+ def to_s
77
+ to_note_name
78
+ end
79
+
80
+ # Returns the note ID - unique for each frequency
81
+ def to_note_id
82
+ "#{octave}:#{name}:#{alter}"
83
+ end
84
+
85
+ def to_str
86
+ to_note_id
87
+ end
88
+
89
+ def inspect
90
+ "\"#{to_str}\""
91
+ end
92
+
93
+ def <=>(other)
94
+ self.to_note_id <=> other.to_note_id
95
+ end
96
+
97
+ end