Read TextGrid labels into a Strings object in Praat - praat

I am stuck trying to figure out how to read the strings from a textgrid which is open in the window but not saved to the hard disk as a raw text file. My goal is to manipulate the strings and save them later.
I want to do something like this but don't really understand how the syntax would work.
tG$ = selectObject: selected$("TextGrid")
stringID = Read Strings from tG
numberOfStrings = Get number of strings
for stringNumber from 0 to numberOfStrings
selectObject: stringID
line$ = Get string: stringNumber
...

You need to loop through the intervals in the TextGrid and use appendFileLine to output the labels to a text file. For example:
# Your need to select the TextGrid manually, and it has only one tier (tier number 1)
outputFile$ = "~/Desktop/output.txt"
writeFile: outputFile$, "" ; start from an empty .txt
numberOfIntervals = Get number of intervals: 1 ; (this is tier 1)
for interval to numberOfIntervals
label$ = Get label of interval: 1, interval
if label$ != "" ; (we just want non-empty intervals)
xmin = Get start time of interval: 1, interval
xmax = Get end time of interval: 1, interval
appendFileLine: outputFile$, "'label$''tab$''xmin''tab$''xmax'"
endif
endfor
This script will output a .txt file with tab delimited values: label, xmin, xmax. You can change the appendFileLine arguments to your needs (tab$ is a predefined variable, which is... a tab).

TextGrid labels are not directly translatable to a Strings object because, unlike a TextGrid, Strings objects do not have tiers. So you could have code that takes all of the labels of a specific tier in a TextGrid and pushes them into a Strings object.
0. Creating an empty Strings
The problem here is that Praat does not want you to populate Strings object yourself, so there is no Create empty Strings.... However, you can subvert one of the existing commands to do this:
Create Strings as tokens: ""
1. Pushing the labels to a Strings object
Now that we have an empty Strings to populate, we can get to work:
procedure labelsToStrings: .tier
.textgrid = selected("TextGrid")
# Make sure this works with interval and point tiers
.item$ = if do("Is interval tier...", .tier) then
... "interval" else "point" fi
# Make the empty Strings
.strings = Create Strings as tokens: ""
Rename: selected$("TextGrid")
# Fetch each label, and insert it to the Strings object
selectObject: .textgrid
.n = do("Get number of " + .item$ + "s...", .tier)
for .i to .n
selectObject: .textgrid
.label$ = do$("Get label of " + .item$ + "...", .tier, .i)
# I assume you don't care about empty labels?
if .label$ != ""
selectObject: .strings
Insert string: 0, .label$
endif
endfor
# Make sure the new object is selected
selectObject: .strings
endproc
2. Profit!
You can try it out:
synth = Create SpeechSynthesizer: "English", "default"
To Sound: "This is some text.", "yes"
sound = selected("Sound")
textgrid = selected("TextGrid")
selectObject: textgrid
#labelsToStrings: 4
removeObject: sound, synth
View & Edit
3. Bonus
If you are interested in getting all the labels in a more manageable package, you might also be interested in the Index specified labels... command from the tgutils plugin, which I also wrote. (I know: I'm amazing at naming things).
That one does something similar to this, but instead of using a Strings object, it dumps all the labels to a Table, as well as the timestamp of points, or the start and end of intervals. And you can also specify subsets of labels to consider using a literal match or a regular expression.
With it, you can re-write #labelsToStrings to look like this:
procedure labelsToStrings: .tier
.name$ = selected$("TextGrid")
runScript: preferencesDirectory$ + "/plugin_tgutils/scripts/" +
... "index_specified_labels.praat", .tier, ".+", "yes"
.table = selected("Table")
Create Strings as tokens: ""
Rename: .name$
for .i to Object_'.table'.nrow
.label$ = Object_'.table'$[.i, "label"]
Insert string: 0, .label$
endfor
removeObject: .table
endproc

Related

vbscript: RegExp Replace turn capture group into variable

regex in VBscript has 3 methods, Test, Extract and Replace, but I can only seem to turn capture groups from Extract into variable.
However what I want to do is use capturing groups from 'Replace' as a variable. I can get a Regex.Replace working with no problems using $1 $2 etc for capturing groups, however I want multiply one of the capture groups.
In an xml file, I want to extract a value, times it by 15, and insert it back in. In this example the tag.
e.q.
strText = "
<rte>
<name>gpx.studio church 2 reduced.gpx</name>
<rtept lat='-33.482652' lon='150.159134'>
<ele>938.4</ele>
<desc>076</desc>
</rtept>
<rtept lat='-33.4825698175265' lon='150.159515440464'>
<ele>942.3</ele>
<desc>162</desc>
</rtept>
<rtept lat='-33.4828785376496' lon='150.159633457661'>
<ele>943.4</ele>
<desc>098</desc>
</rtept>
</rte>
</gpx>"
Dim oRegExp
Set oRegExp = New RegExp
oRegExp.Global=True
oRegExp.Multiline = True
oRegExp.Pattern = strPattern
strPattern = "(<rtept(?:(?:.|\n|\r)*?))<desc>(.*?)<\/desc>((?:(?:.|\n|\r)*?)<\/rtept>)"
strReplace = "$1<desc>$2<\/desc>$3"
' so on this line above, I want to turn the $2 into an integer and multiply it by 15 before putting back into replace.
' I have not done it here because I know it doesnt work as "$2"x1000
strNewText = oRegExp.Replace(strText, strReplace)
I want to turn the $2 into an integer and multiply it by 15 before putting back into replace.
I have tried to get the capture groups as SubMatches(1) which work with Regex.Extract method but it doesnt seem to work in Regex.Replace method, unless I am missing something....
help appreciated

Adding leading Zeros into Day and Month Value

I have a simple table that has a column with a date in this format:
MM/DD/YYYY.
Unfortunately, there are some folks who are working without leading zeros.
Therefore I would like to add a leading zero into the Month and Day element using Power Query to have a common format.
But how? Does someone have any function to share?
Again, not sure why you want to do this, but
Assuming all of the entries are text that looks like dates, you can use the following M-Code:
Split the string on the delimiter
Change each entry in the list to a number
Add 2000 to the last number
Change the numbers back to text with a "00" format
Recombine with the delimiter
let
Source = Excel.CurrentWorkbook(){[Name="Table29"]}[Content],
//set type = Text
#"Changed Type" = Table.TransformColumnTypes(Source,{{"TextDate", type text}}),
xform = Table.TransformColumns(#"Changed Type",
{"TextDate", each
let
x = Text.Split(_,"/"),
y = List.Transform(x,each Number.From(_)),
z = List.ReplaceRange(y,2,1, {2000+y{2}}),
a= List.Transform(z,each Number.ToText(_,"00")),
b = Text.Combine(a,"/")
in b})
in
xform
I am thinking a better solution might be to set up your data entry method so that all dates are entered as dates rather than text

How to use two different excel files in same syntax procedure?

I have an excel file with information about variables (excel1) and another one with information about lists (excel2).
In order to create a syntax to generate a new syntax to create VARIABLE and VALUES LABELS, I used solution proposed by #eli.k here.
But with this solution I have to have a dataset with lists so I could use it instead of writing it “by hand” (copy/paste) (here). One problem came with L2, which has 195 entries so the new create variable would need to be bigger that 20.000 characters (is this possible in SPSS?), appearing all in one line.
What I want to know is if it’s possible to use excel2 automatically in code, line by line.
Using the following code:
GET DATA
/TYPE=XLSX
/FILE=" D:\excel1.xlsx "
/SHEET=name 'Folha1'
/CELLRANGE=FULL
/READNAMES=ON
/DATATYPEMIN PERCENTAGE=95.0.
STRING cmd1 cmd2 (a200).
SORT CASES by List.
MATCH FILES /FILE=* /FIRST=first /LAST=last /BY List. /* marking first and last lines.
DO IF first.
COMPUTE cmd1="VARIABLE LABELS".
COMPUTE cmd2="VALUE LABELS".
END IF.
IF not first cmd1=concat(rtrim(cmd1), " "). /* "/" only appears from the second varname.
COMPUTE cmd1=concat(rtrim(cmd1), " ", Var_label).
COMPUTE cmd2=concat(rtrim(cmd2), " ", Var).
DO IF last.
COMPUTE cmd1=concat(rtrim(cmd1), ".").
COMPUTE cmd2=concat(rtrim(cmd2), " ",' 1 "Afghanistan" 2 "Albania" (…) 195 "Zimbabwe".').
END IF.
EXECUTE.
SELECT IF ('List' 'L2').
ADD FILES /file=* /rename cmd1=cmd /file=* /rename cmd2=cmd.
EXECUTE.
I would like to know if there is a way to replace ' 1 "Afghanistan" 2 "Albania" (…) 195 "Zimbabwe".'' by some function/procedure to grab information from excel2 concerning L2, and showing it line by line:
(…)
VARIABLE LABELS V2 "Country"
/ V3 "Country Mother"
/ V4 "Country Father".
VALUE LABELS V2
V3
V4
1 "Afghanistan"
2 "Albania"
(…)
195 "Zimbabwe".
Thanks for helping me!
This issue is pretty complex and would usually be beyond the scope of Stack-Overflow Q&A but here's my answer anyway:
First I recreate the parts of your example data concerning the value labels only:
data list list/var list (2a5).
begin data
"v1" "L1"
"v2" "L2"
"v3" "L2"
"v4" "L2"
end data.
dataset name xl1.
data list list/list (a5) nb (f5) nb_txt (a20).
begin data
"L1" 1 "Female"
"L1" 2 "Male"
"L2" 1 "Afghanistan"
"L2" 2 "Albania"
"L2" 43 "Israel"
"L2" 195 "Zimbabwe"
end data.
dataset name xl2.
data list list/v1 v2 v3 v4 (4f3).
begin data
1 1 2 3
2 2 2 43
1 2 1 195
end data.
dataset name gen.
Now to work:
The first part is to create a macro for each list of variable labels. since some of the lists are long, I use ADD Value labels separately for each value.
dataset activate xl2.
string cmd (a200) cmdFin (a20).
sort cases by list nb.
match files /file=* /by list /first=first /last=last.
compute cmd=concat("add value labels !1 ", string(nb,f6), " '", rtrim(nb_txt), "' .").
if first cmd=concat("define dolist_", list, " (!pos=!cmdend) ", rtrim(cmd)).
if last cmdFin=" !enddefine .".
write outfile="path\create value label macros.sps"/cmd/cmdfin.
exe.
insert file="path\create value label macros.sps".
After inserting the generated syntax a macro has been defined for each of the value lists. Now we create an additional syntax that will run the related macro for each of the variable names in the list:
dataset activate xl1.
string cmd (a200).
compute cmd=concat("dolist_", list, " ", var, " .").
write outfile="path\run value label macros.sps"/cmd.
exe.
Now we can actually try out the generated macros on our original data:
dataset activate gen.
insert file="path\run value label macros.sps".

VB Control Naming by Variable

Dim LineNo as Integer
LineNo = CStr(channel) 'This can have a value of 1 to 100
If LineNo = 1 then
Text1.Text = "Line one selected"
Elseif LineNo = 2 then
Text2.Text = "Line one selected"
'Etc etc
End if
I need to replace the number "1" in Text1.Text and every other TextBox with the value of LineNo? For example:
Text{LineNo}.Text
So I would not have to do a repeated "If" and have a smaller one line code like this:
Text{LineNo}.Text = "Line " & LineNo & " selected"
How would I do this?
Look into a Control array of text boxes. You could have txtLine(), for example, indexed by the channel number.
LineNo = CStr(channel)
txtLine(channel).Text = "Line " & LineNo & " selected"
To create the array, set the Index property of each of the text boxes to an increasing integer, starting at 0.
If you have a finite and relatively small number, you can use a property. I've used this approach with up to 30+ elements in a pinch. Super simple, easy pattern to recognize and replicate in other places. A bit if a pain if the number of elements changes in the future, but extensible nevertheless.
It uses the Choose statement, which takes an index N and returns the Nth element (1-based), hence, the check makes sure that N is > 0 and <= MAX (which you would configure).
Public Property Get TextBox txt(ByVal N As Long)
Const MAX As Long = 10
If N <= 0 || N > MAX Then Exit Property ' Will return a "Nothing". You could return the bound element if you prefer
set txt = Choose(Text1, Text2, Text3, Text4, Text5, Text6, Text7, Text8, Text9, Text10)
End Property
Then, you can simply reference them with the Property, much like an alias:
txt(1).Text = "Line 1 text"
txt(2).Text = "Line 2 text"
If you have an arbitrary number, then you are likely using a control array already, which is simpler because it can be referenced by Index already, so you can directly reference it.
If neither of these work for you and you have a very large number of controls, you can scan the Controls collection in a similar Property, attempting to match ctrl.Name with the pattern of your choice (e.g., matching the first 4 characters to the string "Text", thus matching Text1, Text2, etc, for an unlimited number). Theoretically, this should be future-proofed, but that's just theoretical, because anything can happen. What it does do for you is to encapsulate the lookup in a way that "pretends" to be a control array. Same syntax, just you control the value.
Public Property Get TextBox txt(ByVal N As Long)
Dim I As Long
For I = 0 To Controls.Count - 1 ' Controls is zero-based
' Perform whatever check you need to. Obviously, if you have a "Label" named
' "Text38", the assignment will throw an error (`TextBox = Label` doesn't work).
If Left(Controls(I).Name, 4) = "Text" Then
set txt = Controls(I)
End If
Next
' If you want the Property to never return null, you could uncomment the following line (preventing dereference errors if you insist on the `.Text` for setting/getting the value):
' If txt Is Nothing Then Set txt = Text1
End Property
Use the same way as above: txt(n).Text = "..."

Excel Power Query: How to Combine All List Items into Single Row

I have a query to the Cognitive text keyphase API from Microsoft from '16 Excel Power Query - getting keywords from tweets. Works fine.
However, the JSON doc that's returned per query is converted by Power Query into a list of ~1-5 rows.
In the case of the pic, I want all responses returned to be in one cell/row, regardless of the number of items returned.
Here is my full M query (you need to put your own key in) if you're interested.
let
TweetCognitive = (TweetID as text, TweetText as text) =>
let
JsonRecords = Text.FromBinary(Json.FromValue([id=TweetID, text=TweetText])),
JsonRequest = "{""documents"": [" & JsonRecords & "]}",
JsonContent = Text.ToBinary(JsonRequest, TextEncoding.Ascii),
Response =
Web.Contents("https://westus.api.cognitive.microsoft.com/text/analytics/v2.0/keyPhrases?",
[
Headers = [#"Ocp-Apim-Subscription-Key"="yourkeyhere",
#"Content-Type"="application/json", Accept="application/json"],
Content=JsonContent
]),
JsonResponse = Json.Document(Response,1252)
in
JsonResponse
in
TweetCognitive
You can use List.Accumulate to turn a list of values into a single value. For example, this would combine the values in the list into a single text value with ". " separating each row's value:
List.Accumulate(JsonResponse, "", (state, current) => state & current & ". ")
This would generate "monday frank love happiness today. nice good kind. tomorrow. " in your example. If you want to get rid of the trailing space, you can surround the List.Accumulate expression with Text.Trim.
The basic function to concatenate elements in a list is Text.Combine. For instance:
Text.Combine(JsonResponse, " ")
This avoids the extra delimeter at the end you get with List.Accumulate. Note also List.Combine is for creating a longer combined list from shorter lists, and the similar naming there may cause confusion.

Resources