praatrb 1.0.0

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: bba80e61081fe0185a199334134bd12cb0efc4d5
4
+ data.tar.gz: 3546c648b01c3bdfeee01dc2cf610766e37b3723
5
+ SHA512:
6
+ metadata.gz: b83719ea8f9f5635ed2f314d0f6621cfff5b287f03ca4958e6932a1ea387dbb1eccb422862b06efe86b8e08bed56943478002d387a728bb2e49feb1491e11912
7
+ data.tar.gz: 6a29741d111d46608260172c59398c959bf5396db4e8a1412db2f09dde5e73d2f148c8e6b3ef29d9f0872141988ce2231dfbbb14a53abf1a88fc6517067737a4
data/.autotest ADDED
@@ -0,0 +1,25 @@
1
+ # -*- ruby -*-
2
+
3
+ require "autotest/restart"
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.testlib = "minitest/unit"
7
+ #
8
+ # at.extra_files << "../some/external/dependency.rb"
9
+ #
10
+ # at.libs << ":../some/external"
11
+ #
12
+ # at.add_exception "vendor"
13
+ #
14
+ # at.add_mapping(/dependency.rb/) do |f, _|
15
+ # at.files_matching(/test_.*rb$/)
16
+ # end
17
+ #
18
+ # %w(TestA TestB).each do |klass|
19
+ # at.extra_class_map[klass] = "test/test_misc.rb"
20
+ # end
21
+ # end
22
+
23
+ # Autotest.add_hook :run_command do |at|
24
+ # system "rake build"
25
+ # end
data/.gemtest ADDED
File without changes
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2014-11-04
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,20 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/praat_lex
7
+ lib/praat.rb
8
+ lib/praat_formant.rb
9
+ lib/praat_lexer.rb
10
+ lib/praat_lexer.rex
11
+ lib/praat_lexer.rex.rb
12
+ lib/praat_parser.rb
13
+ lib/praat_pitch.rb
14
+ test/fixtures/tajm.Pitch
15
+ test/test.Pitch
16
+ test/test_praat.rb
17
+ test/test_praat_formant.rb
18
+ test/test_praat_lexer.rb
19
+ test/test_praat_parser.rb
20
+ test/test_praat_pitch.rb
data/README.txt ADDED
@@ -0,0 +1,74 @@
1
+ = praat_lex
2
+
3
+ * http://www.andrewchristophersmith.com/
4
+
5
+ == DESCRIPTION:
6
+
7
+ Provides a very malleable Praat file parser
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Pro: cool idea, doesn't need file specification
12
+ * Con: only sort of works, not really tested
13
+
14
+ The parser parses the basic grammar of Praat files, creating classes whenever
15
+ it needs them. It results in a tree of Objects and Collections, with a variety
16
+ of attributes that correspond to the grammar it finds in the file. It knows
17
+ nothing about Praat. It also only works with long text files.
18
+
19
+ == SYNOPSIS:
20
+
21
+ require 'praat'
22
+ my_collection = Praat.parse_file("my_praat_file.Collection")
23
+ # => #<Praat::Root ... >
24
+ my_collection.items[0]
25
+ # => #<Praat::Item ... >
26
+ # Let's say the first item in the collection was a Pitch object
27
+ # Collection of frames
28
+ my_collection.items[0].frames[0].candidates[0].frequency
29
+ # => 101.2358493290
30
+
31
+ == REQUIREMENTS:
32
+
33
+ * oedipux_lex
34
+
35
+ == INSTALL:
36
+
37
+ This is experimental. Only git-cloners allowed at this point.
38
+
39
+ git clone https://github.com/andrewcsmith/praatrb.git
40
+ rake install_gem
41
+
42
+ == DEVELOPERS:
43
+
44
+ After checking out the source, run:
45
+
46
+ $ rake newb
47
+
48
+ This task will install any missing dependencies, run the tests/specs,
49
+ and generate the RDoc.
50
+
51
+ == LICENSE:
52
+
53
+ (The MIT License)
54
+
55
+ Copyright (c) 2014 Andrew Christopher Smith
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining
58
+ a copy of this software and associated documentation files (the
59
+ 'Software'), to deal in the Software without restriction, including
60
+ without limitation the rights to use, copy, modify, merge, publish,
61
+ distribute, sublicense, and/or sell copies of the Software, and to
62
+ permit persons to whom the Software is furnished to do so, subject to
63
+ the following conditions:
64
+
65
+ The above copyright notice and this permission notice shall be
66
+ included in all copies or substantial portions of the Software.
67
+
68
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
69
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
70
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
71
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
72
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
73
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
74
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ # -*- ruby -*-
2
+
3
+ require "rubygems"
4
+ require "hoe"
5
+ require "oedipus_lex"
6
+
7
+ # Hoe.plugin :compiler
8
+ # Hoe.plugin :email
9
+ # Hoe.plugin :gem_prelude_sucks
10
+ # Hoe.plugin :history
11
+ # Hoe.plugin :inline
12
+ Hoe.plugin :minitest
13
+ # Hoe.plugin :perforce
14
+ # Hoe.plugin :racc
15
+ # Hoe.plugin :rcov
16
+ # Hoe.plugin :rdoc
17
+ # Hoe.plugin :rubygems
18
+ # Hoe.plugin :seattlerb
19
+ # Hoe.plugin :travis
20
+
21
+ Hoe.spec "praatrb" do
22
+ developer "Andrew Smith", "andrewchristophersmith@gmail.com"
23
+ dependency "oedipus_lex", "~> 2.4", :developer
24
+
25
+ self.group_name = "Andrew Smith" # if part of an organization/group
26
+
27
+ license "MIT" # this should match the license in the README
28
+ end
29
+
30
+ Rake.application.rake_require "oedipus_lex"
31
+ task :lexer => "lib/praat_lexer.rex.rb"
32
+ task :parser => :lexer
33
+ task :test => :parser
34
+
35
+ file "lib/praat_lexer.rex.rb" => "lib/praat_lexer.rex"
36
+
37
+ # vim: syntax=ruby
data/bin/praat_lex ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # Accepts one argument: the name of the file to lex and parse. Second optional
3
+ # argument is the encoding of that file.
4
+
5
+ require 'praat'
6
+ require 'pp'
7
+
8
+ source_encoding = ARGV[1] || "utf-16"
9
+
10
+ begin
11
+ f = File.open(ARGV[0], "rb", {encoding: "#{source_encoding}:utf-8"})
12
+ rescue Encoding::InvalidByteSequenceError => e
13
+ # If the file reading throws an error, try with the default (utf-8) encoding.
14
+ if source_encoding != "utf-8"
15
+ source_encoding = "utf-8"
16
+ retry
17
+ else
18
+ raise e
19
+ end
20
+ end
21
+
22
+ s = f.read
23
+
24
+ begin
25
+ lexer = Praat::Lexer.new
26
+ parser = Praat::Parser.new
27
+
28
+ lexed = lexer.parse(s)
29
+ parsed = parser.parse(lexed)
30
+ rescue Praat::Lexer::ScanError => e
31
+ puts /(.*\n){0,3}/.match(e.message)[0]
32
+ puts e.backtrace
33
+ exit
34
+ end
35
+
data/lib/praat.rb ADDED
@@ -0,0 +1,88 @@
1
+ require "praat_lexer.rb"
2
+ require "praat_parser.rb"
3
+ require "praat_pitch.rb"
4
+ require "praat_formant.rb"
5
+
6
+ module Praat
7
+ VERSION = "1.0.0"
8
+
9
+ # Parses a file given a specified encoding, returning an object containing
10
+ # nested objects
11
+ def self.parse_file filename, encoding = 'utf-8'
12
+ f = File.open(filename, "rb", {encoding: "#{encoding}:utf-8"})
13
+ Praat::Parser.new.parse(Praat::Lexer.new.parse(f.read))
14
+ end
15
+
16
+ # Turns a hz reading into a midi value.
17
+ #
18
+ # hz - The input value in hz
19
+ # base_hz - The frequency for tuning (i.e., A=440)
20
+ # base_midi - The midi key that the tuning pitch corresponds to
21
+ def self.hz_to_midi hz, base_hz = 440.0, base_midi = 69
22
+ if hz && hz > 0.0
23
+ (Math.log2(hz.to_f / base_hz) * 12.0) + base_midi
24
+ else
25
+ 0.0
26
+ end
27
+ end
28
+
29
+ # Something will be either a collection or an object. Only an object can have
30
+ # properties.
31
+ class MetaCollection < Array
32
+ attr_accessor :parent
33
+
34
+ def << object
35
+ object.parent = self
36
+ super object
37
+ end
38
+
39
+ def to_s
40
+ self.inspect << super
41
+ end
42
+ end
43
+
44
+ class MetaObject
45
+ attr_accessor :parent
46
+
47
+ # Append the object to the collection of names
48
+ def add_to_collection name, object
49
+ instance_variable_get("@#{name}s").instance_exec(object) { |o| self << o }
50
+ end
51
+
52
+ # Add the property to the object
53
+ def add_property name, value
54
+ # Convert it to snake-case
55
+ name = sanitize_name name.to_s
56
+
57
+ # Add the attr_accessor if it doesn't exist
58
+ unless self.respond_to? "#{name}"
59
+ self.class.class_exec(name) do |n|
60
+ attr_accessor n.to_sym
61
+ end
62
+ end
63
+
64
+ if value.respond_to? :parent=
65
+ value.parent = self
66
+ end
67
+
68
+ # Set the attribute to the value
69
+ self.send("#{name}=", value)
70
+ end
71
+
72
+ def to_s
73
+ "#{self.inspect}"
74
+ end
75
+
76
+ private
77
+
78
+ def sanitize_name name
79
+ if name == "class"
80
+ name = "klass"
81
+ end
82
+ name.downcase.sub(' ', '_')
83
+ end
84
+ end
85
+
86
+ class Root < MetaObject; end
87
+ end
88
+
@@ -0,0 +1,19 @@
1
+ module Praat
2
+ def self.dominant_frame item
3
+ dominant_frame = item.frames.max {|a, b|
4
+ a.intensity <=> b.intensity
5
+ }
6
+ item.add_property :dominant_frame, dominant_frame
7
+ item
8
+ end
9
+
10
+ class MetaObject; end
11
+ class Item < MetaObject
12
+ def map_formant_frequencies
13
+ self.framess.map {|frame|
14
+ frame.formants.map(&:frequency)
15
+ }
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,40 @@
1
+ module Praat; end
2
+
3
+ require "praat_lexer.rex.rb"
4
+
5
+ class Praat::Lexer
6
+ def do_parse
7
+ output = []
8
+ while token = next_token do
9
+ type, *vals = token
10
+ output << send("lex_#{type}", *vals)
11
+ end
12
+ output
13
+ end
14
+
15
+ def lex_float_property property, value
16
+ [:property, property.chomp, value.to_f]
17
+ end
18
+
19
+ def lex_integer_property property, value
20
+ [:property, property.chomp, value.to_i]
21
+ end
22
+
23
+ def lex_string_property property, value
24
+ [:property, property.chomp, value.chomp]
25
+ end
26
+
27
+ def lex_collection collection
28
+ [:collection, collection.chomp]
29
+ end
30
+
31
+ def lex_object object, index
32
+ [:object, object.chomp, index.to_i]
33
+ end
34
+
35
+ def lex_indent spaces
36
+ indents = spaces.length / 4
37
+ [:indent, indents]
38
+ end
39
+ end
40
+
@@ -0,0 +1,24 @@
1
+ module Praat; end
2
+
3
+ class Praat::Lexer
4
+ macros
5
+ INTEGER /\d+/
6
+ FLOAT /\d+\.\d+(?:e-\d\d)?/ # also captures scientific notation
7
+ NUMBER /#{FLOAT}|#{INTEGER}/
8
+ LETTER /[\w\u0250-\u02AF\u00E6\u00F0\u03B8\u014B]/ # includes IPA symbols
9
+ WORD /#{LETTER}+(?: #{LETTER}*)?/ # words may optionally have a space
10
+ rules
11
+ # Parse various tokens
12
+ /(#{WORD}) = (#{FLOAT})/ { [:float_property, *matches] }
13
+ /(#{WORD}) = (#{INTEGER})/ { [:integer_property, *matches] }
14
+ /(#{WORD}) = "(#{WORD})"/ { [:string_property, *matches] }
15
+ /(#{WORD}) \[\]:/ { [:collection, *matches] }
16
+ /(#{WORD}) \[(#{INTEGER})\]:/ { [:object, *matches] }
17
+ /( {4}+)/ { [:indent, *matches] }
18
+
19
+ # Trailing whitespace and empty lines
20
+ /\s*\n/
21
+ end
22
+
23
+ # vim: filetype=ruby
24
+
@@ -0,0 +1,100 @@
1
+ # encoding: UTF-8
2
+ #--
3
+ # This file is automatically generated. Do not modify it.
4
+ # Generated by: oedipus_lex version 2.4.0.
5
+ # Source: lib/praat_lexer.rex
6
+ #++
7
+
8
+ module Praat; end
9
+
10
+ class Praat::Lexer
11
+ require 'strscan'
12
+
13
+ INTEGER = /\d+/
14
+ FLOAT = /\d+\.\d+(?:e-\d\d)?/
15
+ NUMBER = /#{FLOAT}|#{INTEGER}/
16
+ LETTER = /[\w\u0250-\u02AF\u00E6\u00F0\u03B8\u014B]/
17
+ WORD = /#{LETTER}+(?: #{LETTER}*)?/
18
+
19
+ class ScanError < StandardError ; end
20
+
21
+ attr_accessor :filename
22
+ attr_accessor :ss
23
+ attr_accessor :state
24
+
25
+ alias :match :ss
26
+
27
+ def matches
28
+ m = (1..9).map { |i| ss[i] }
29
+ m.pop until m[-1] or m.empty?
30
+ m
31
+ end
32
+
33
+ def action
34
+ yield
35
+ end
36
+
37
+
38
+ def scanner_class
39
+ StringScanner
40
+ end unless instance_methods(false).map(&:to_s).include?("scanner_class")
41
+
42
+ def parse str
43
+ self.ss = scanner_class.new str
44
+ self.state ||= nil
45
+
46
+ do_parse
47
+ end
48
+
49
+ def parse_file path
50
+ self.filename = path
51
+ open path do |f|
52
+ parse f.read
53
+ end
54
+ end
55
+
56
+ def next_token
57
+
58
+ token = nil
59
+
60
+ until ss.eos? or token do
61
+ token =
62
+ case state
63
+ when nil then
64
+ case
65
+ when text = ss.scan(/(#{WORD}) = (#{FLOAT})/) then
66
+ action { [:float_property, *matches] }
67
+ when text = ss.scan(/(#{WORD}) = (#{INTEGER})/) then
68
+ action { [:integer_property, *matches] }
69
+ when text = ss.scan(/(#{WORD}) = "(#{WORD})"/) then
70
+ action { [:string_property, *matches] }
71
+ when text = ss.scan(/(#{WORD}) \[\]:/) then
72
+ action { [:collection, *matches] }
73
+ when text = ss.scan(/(#{WORD}) \[(#{INTEGER})\]:/) then
74
+ action { [:object, *matches] }
75
+ when text = ss.scan(/( {4}+)/) then
76
+ action { [:indent, *matches] }
77
+ when text = ss.scan(/\s*\n/) then
78
+ # do nothing
79
+ else
80
+ text = ss.string[ss.pos .. -1]
81
+ raise ScanError, "can not match (#{state.inspect}): '#{text}'"
82
+ end
83
+ else
84
+ raise ScanError, "undefined state: '#{state}'"
85
+ end # token = case state
86
+
87
+ next unless token # allow functions to trigger redo w/ nil
88
+ end # while
89
+
90
+ raise "bad lexical result: #{token.inspect}" unless
91
+ token.nil? || (Array === token && token.size >= 2)
92
+
93
+ # auto-switch state
94
+ self.state = token.last if token && token.first == :state
95
+
96
+ token
97
+ end # def next_token
98
+ end # class
99
+
100
+ # vim: filetype=ruby
@@ -0,0 +1,56 @@
1
+ module Praat; end
2
+
3
+ class Praat::Parser
4
+ def parse input
5
+ output = Praat::Root.new
6
+ @current_node = output
7
+ @current_indent = 0
8
+ input.each do |item|
9
+ case item.shift
10
+ when :indent
11
+ process_indent item
12
+ when :collection
13
+ @current_node.add_property "#{item.first}s", create_collection(item.first)
14
+ @current_node = @current_node.send("#{item.first}s")
15
+ when :object
16
+ @current_node << create_object(item.first)
17
+ @current_node.last.parent = @current_node
18
+ @current_node = @current_node.last
19
+ when :property
20
+ @current_node.add_property(*item)
21
+ end
22
+ end
23
+ output
24
+ end
25
+
26
+ private
27
+
28
+ def process_indent item
29
+ indent = item.first
30
+ # If the new indent is a backstep
31
+ if indent < @current_indent
32
+ # Go back
33
+ (@current_indent - indent).times do
34
+ @current_node = @current_node.parent
35
+ end
36
+ end
37
+ @current_indent = item.first
38
+ end
39
+
40
+ def create_collection klass
41
+ klass = klass.capitalize << "s"
42
+ unless Object.const_defined? "Praat::#{klass}"
43
+ Praat.class_eval "class #{klass} < Praat::MetaCollection; end"
44
+ end
45
+ (Praat.const_get "#{klass}").new
46
+ end
47
+
48
+ def create_object klass
49
+ klass = klass.capitalize
50
+ unless Object.const_defined? "Praat::#{klass}"
51
+ Praat.class_eval "class #{klass} < Praat::MetaObject; end"
52
+ end
53
+ (Praat.const_get "#{klass}").new
54
+ end
55
+ end
56
+
@@ -0,0 +1,20 @@
1
+ module Praat
2
+ def self.find_dominant_pitch item
3
+ item.frames.map! do |frame|
4
+ top = frame.candidates.max do |a, b|
5
+ a.strength <=> b.strength
6
+ end
7
+
8
+ frame.add_property "freq", top.frequency
9
+
10
+ # Filter out the unvoiced candidates
11
+ if frame.freq > item.ceiling
12
+ frame.freq = nil
13
+ end
14
+
15
+ frame
16
+ end
17
+ item
18
+ end
19
+ end
20
+