praatrb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+