CSV data hopefully in shell

I would like to delete all rows in a csv = 75% of the lowest readings from column 3 . Is this possible using some thing with “awk -F ‘,’ '$3>= ….” Or would I have to sort the column with eg “sort -k 3,3” then count the rows divide the no of rows by 100 times 25 = x then use awk or sed to print only the first x number of rows in the csv. Thanks in advance. If system events can do the same as quickly,then AppleScript is fine

I’m analysing CSV files generated from the analysis of a short audio sample that contain estimated pitch readings, every 10ms throughout the sample, each frame’s frequency estimation has a probability reading from 0.0 to 1.0. I want to take the top 25% of readings with the highest probability so I can manipulate the remaining readings and set the most popular of the frequencies as a tag.

FWIW, I think that sorting early makes things easier. You can use commands like head to keep only those above the line and wc to determine where that line is. As it’s kind of awkward to work on data when you don’t know what it looks like, I’ve used a small table as data for this (see below). The script requires a csv on the desktop named ‘passing.csv’.

The script will run wc on the data to determine how many lines it has and uses this to calculate the top 25%. Of course, you could also just arbitrarily insert a number.

Next, it removes header and footer rows, sorts the data based on column 5 in descending order, keeps only the top 25% and then outputs to top, which it creates on the desktop.

It then retrieves the header row from the source data and finally concatenates the header row and top data into final. If your data doesn’t have headers or footers, adjust accordingly.

set csv to quoted form of (POSIX path of (path to desktop as text) & "passing.csv")
set h2 to quoted form of (POSIX path of (path to desktop as text) & "header2.csv")
set top to quoted form of (POSIX path of (path to desktop as text) & "top.csv")
set final to quoted form of (POSIX path of (path to desktop as text) & "final.csv")

-- calculate top 25%
set counts to do shell script "wc " & csv & " | tr -s ' '"
set AppleScript's text item delimiters to space
set lineCounts to ((text item 2 of counts as integer) - 5) / 4 as integer -- minus for headers and footers

-- remove headers,footers, sort on column 5, reverse order, keep top 25%, output to top
do shell script "cat " & csv & " | head -n 34 | tail -n 32 | sort -t, -k5,5 -gr | head -n " & lineCounts & " > " & top

-- extract header from csv to h2
set head2 to do shell script "cat " & csv & " | head -n 2 | tail -n 1" & " > " & h2

-- concatenate h2 & top to final
do shell script "cat " & h2 & space & top & " > " & final

You could add something like this to the main line to replace commas with tabs:

sed 's/,/    /g'

The guts of it as a shell command would be this:

cat passing.csv | head -n 34 | tail -n 32 | sort -t, --key=5,5 -gr | head -n8

The output will be the top eight teams in total yardage.

Source data:

",,,,,Tot Yds & TO,Tot Yds & TO
Rk,Tm,G,PF,Yds,Ply,Y/P
1,Kansas City Chiefs,17,496,7032,1094,6.4
2,Philadelphia Eagles,17,477,6614,1124,5.9
3,Dallas Cowboys,17,467,6034,1114,5.4
4,Buffalo Bills,16,455,6361,1037,6.1
5,Detroit Lions,17,453,6460,1092,5.9
6,San Francisco 49ers,17,450,6216,1047,5.9
7,Minnesota Vikings,17,424,6145,1123,5.5
8,Cincinnati Bengals,16,418,5768,1053,5.5
9,Seattle Seahawks,17,407,5976,1044,5.7
10,Jacksonville Jaguars,17,404,6075,1072,5.7
11,Miami Dolphins,17,397,6197,1009,6.1
12,Las Vegas Raiders,17,395,5993,1049,5.7
13,Los Angeles Chargers,17,391,6108,1154,5.3
14,Green Bay Packers,17,370,5745,1051,5.5
15,Atlanta Falcons,17,365,5417,1011,5.4
16,New York Giants,17,365,5676,1089,5.2
17,New England Patriots,17,364,5348,1006,5.3
18,Cleveland Browns,17,361,5934,1116,5.3
19,Baltimore Ravens,17,350,5760,1052,5.5
20,Carolina Panthers,17,347,5206,976,5.3
21,Arizona Cardinals,17,340,5499,1144,4.8
22,New Orleans Saints,17,330,5674,1015,5.6
23,Chicago Bears,17,326,5233,993,5.3
24,Washington Commanders,17,321,5615,1140,4.9
25,Tampa Bay Buccaneers,17,313,5894,1159,5.1
26,Pittsburgh Steelers,17,308,5484,1109,4.9
27,Los Angeles Rams,17,307,4769,1001,4.8
28,Tennessee Titans,17,298,5045,992,5.1
29,New York Jets,17,296,5409,1074,5.0
30,Houston Texans,17,289,4820,1015,4.7
31,Indianapolis Colts,17,289,5298,1103,4.8
32,Denver Broncos,17,287,5527,1078,5.1
,Avg Team,,370.6,5760.4,1066.8,5.4
,League Total,,11860,184332,34136,5.4
,Avg Tm/G,,21.9,340.1,63.0,5.4"

Thank you very much Mockman, that works a treat! The issues I’m now experiencing with the script are after running your routine, I run…

`do shell script "cat " & h2 & space & top & " > " & final
set recCsv to "/Users/md/Desktop/final.csv"

set my_data to read recCsv


set csvData to paragraphs 2 thru -2 of my_data as list

set freqData to {}

set oldDelims to AppleScript's text item delimiters
set AppleScript's text item delimiters to ","

repeat with a_reading in csvData


set y to (text item 2 of a_reading)`
    -- set y to y as integer

each csv result from column 2 should run through up to 127 if else statements that rounds the frequency reading in hz off to the closest note eg: 440hz = A3 , 17.9hz = D0

`if y ≥ 15.9 and y ≤ 16.8 then
	set y to "C0"
else if y ≥ 16.81 and y ≤ 17.8 then
	set y to "C#0"
else if y ≥ 17.81 and y ≤ 18.8 then
	set y to "D0"
set end of freqData to y

end repeat`

But if I process the reading “581.148” I think it records it as 58.?

`else if y ≥ 56.61 and y ≤ 59.12 then
	set y to "A#1"`

instead of

`else if y ≥ 562.44 and y ≤ 595.87 then
	set y to "D5"`

Would setting y to y as integer disregard the decimal values? As you can see in the snippet above, the decimal values in the low end of the spectrum are needed to ensure accurate pitch estimation
And (although you solved the question I asked) I think if the csv contains a total number of rows not divisible by 4 it throws an error, Im guessing that’s because it can’t return 25% of a list that eg has only 3 rows. Other than that it all works well

I have to step out so I’ll look at this later but briefly regarding the integer… in my script, I used it solely to tell the head command how many lines it should list. The linecounts variable wasn’t used anywhere else.

As to what integer does to a number…

set y to "581.148"
y as integer
--> 581

Have you thought about using position in a list rather than a long series of if…then statements?

For example:

set freqList to {15.9, 16.8, 17.8, 18.8, 56.6, 59.6, 562.44, 595.87}
set noteList to {"C0", "C#0", "D0", "D#0", "A#1", "B1", "D5", "D#5"}

set y to 581.148
set y to 18
set y to 57.5

repeat with xx from 1 to length of freqList
	if y is greater than item xx of freqList then
	else
		set zz to item (xx - 1) of noteList
		exit repeat
	end if
end repeat

zz

-- "D5"
-- "D0"
-- "A#1"

Regarding integers, yes, converting the frequency to an integer prior to calculations could affect which note it is designated as.

Consider the following two examples:

set y to 56.7
if y ≥ 56.61 and y ≤ 59.12 then set y to "A#1"
--> "A#1"

set y to 56.7
set y to y as integer --> 57
if y ≥ 56.61 and y ≤ 59.12 then set y to "A#1"
--> "A#1"

set y to 56.6
if y ≥ 56.61 and y ≤ 59.12 then set y to "A#1"
y
--> 56.6 -- will continue on to test against B1 frequencies

set y to 56.6
set y to y as integer --> 57
if y ≥ 56.61 and y ≤ 59.12 then set y to "A#1"
--> "A#1"

This is because of the default rounding that occurs when you coerce a real to an integer:

set y to 56.5
set y to y as integer --> 56

set y to 56.5001
set y to y as integer --> 57

So every time you have a frequency that lies in the gap between .5 and a note’s boundary, coercing to an integer will produce an error. Offhand, I don’t think you need to convert to integers here though.

Many thanks Mockman for the help and advice. I’ve created a droplet app for folder batch processing but here’s the script so far for a single file. It’s slow but it works

The script analyses a .wav file using a convolutional pitch estimation engine, then takes the top 25% of the CSV’s probability readings generated using crepe, rounds and converts the frequency readings in hz to the closest f0 note, then writes a tag with the note info to the audio file. Any thoughts on how to speed it up.

`use AppleScript version "2.4" -- Yosemite (10.10) or later
 use framework "Foundation"
 use scripting additions

 property f0 : {}
 property fileName : {}

tell application "Finder"

-- delete previous files
set volume alert volume 0
delete (every item of folder "HD:Users:me:Desktop:conv:" whose name contains "passing")
delete (every item of folder "HD:Users:me:Desktop:conv:" whose name extension is "csv")
delay 0.5

-- choose file, store original file path as property, duplicate file to "passing.wav" in desktop folder "conv" for analysis
set fileName to (choose file with prompt "Please Select a WAV file for Processing")

set audioWav to fileName
set audio_wav to duplicate audioWav to "HD:Users:me:Desktop:conv:"



set f to (the first file of ¬
         container (alias "HD:Users:me:Desktop:conv:") of ¬
	application "Finder" whose name ends with "wav")
set ti to text items of (get name of f)
if number of ti is 1 then
	set name of f to "passing.wav"
else
	set name of f to "passing" & "." & "wav"
end if
end tell

 -- perform pitch analysis on dupe file, create csv file with estimated pitch in hz and probability readings taken every 10ms to length of audio file using crepe
do shell script "/Users/md/Library/Python/3.9/bin/crepe filename ~/Desktop/conv/passing.wav -v -V	 -c 'full' -s '10'"

-- return the top 25% of crepe's csv "passing.f0.csv"'s probability readings
set csv to quoted form of (POSIX path of (path to desktop as text) & "conv/" & "passing.f0.csv")
set h2 to quoted form of (POSIX path of (path to desktop as text) & "conv/" & "header2.csv")
set top to quoted form of (POSIX path of (path to desktop as text) & "conv/" & "top.csv")
set final to quoted form of (POSIX path of (path to desktop as text) & "conv/" & "final.csv")

-- calculate top 25%
set counts to do shell script "wc " & csv & " | tr -s ' '"
set AppleScript's text item delimiters to space
set lineCounts to ((text item 2 of counts as integer) - 5) / 4 as integer -- minus for headers and footers

-- remove headers,footers, sort on column 5, reverse order, keep top 25%, output to top
do shell script "cat " & csv & " | head -n 34 | tail -n 32 | sort -t, -k3,3 -gr | head -n " & lineCounts & " > " & top

-- extract header from csv to h2
set head2 to do shell script "cat " & csv & " | head -n 2 | tail -n 1" & " > " & h2

-- concatenate h2 & top to final
do shell script "cat " & h2 & space & top & " > " & final
set recCsv to "/Users/me/Desktop/conv/final.csv"

-- read csv final with top 25% of readings
set my_data to read recCsv

-- loop through column 2 of csv results and convert each frequency in hz to the closest f0 note 
set csvData to paragraphs 2 thru -2 of my_data as list

set freqData to {}
set freqList to {15.9, 16.81, 17.81, 18.81, 19.91, 21.22, 22.48, 23.81, 25.23, 26.72, 28.31, 30.0, 31.79, 33.67, 35.67, 37.79, 40.41, 42.41, 44.94, 47.61, 50.44, 53.44, 56.61, 59.13, 62.66, 66.37, 70.32, 74.5, 78.92, 83.62, 88.59, 93.86, 99.44, 105.35, 111.61, 118.25, 125.28, 132.72, 140.62, 145.98, 157.83, 167.22, 177.16, 187.7, 198.86, 210.68, 223.21, 236.48, 250.54, 265.45, 281.22, 297.94, 315.67, 334.44, 354.32, 375.38, 397.71, 421.35, 446.41, 472.95, 501.07, 530.87, 562.44, 595.88, 631.31, 668.86, 708.63, 750.76, 795.4, 842.7, 892.81, 945.89, 1002.15, 1061.73, 1124.87, 1191.76, 1262.62, 1337.7, 1417.25, 1501.52, 1590.8, 1685.39, 1785.61, 1891.79, 2004.27, 2123.45, 2249.72, 2383.5, 2525.23, 2675.39, 2834.48, 3003.15, 3181.58, 3370.78, 3571.21, 3783.56, 4008.55, 4246.91, 4499.44, 4766.99, 5050.45, 5350.76, 5668.93, 6006.03, 6363.17, 6741.54, 7142.41, 7567.12}
set noteList to {"C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0", "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1", "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3", "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A4#", "B4", "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5", "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6", "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7", "C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8"}

set oldDelims to AppleScript's text item delimiters
set AppleScript's text item delimiters to ","

repeat with a_reading in csvData
    	set y to (text item 2 of a_reading)
       set y to y as integer

repeat with xx from 1 to length of freqList
	if y is greater than item xx of freqList then
	else
		set zz to item (xx - 13) of noteList -- for some reason there was an octave jump so had to -13 semitones
		exit repeat
	end if
end repeat

zz
set y to zz



set y to y as text

set end of freqData to y

end repeat

set L to freqData

-- find the most popular note in the list L and store it as property f0


set theList to {}
repeat 50 times
    	set theList to theList & items of L
end repeat


mostPopularItemInList(theList)

to mostPopularItemInList(x)
    script accellerator
	property theList : x
	property originalItems : {}
	property originalItemsCount : {}
	on indexof(theItem, theList) 
		set text item delimiters to return
		set theList to return & theList & return
		set text item delimiters to {""}
		try
			-1 + (count (paragraphs of (text 1 thru (offset of (return & theItem & return) in theList) of theList)))
		on error
			0
		end try
	end indexof
	to greaterInteger()
		set largerItem to 0
		set largerItemIndex to 0
		repeat with i from 1 to count originalItemsCount
			if originalItemsCount's item i > largerItem then
				set largerItem to originalItemsCount's item i
				set largerItemIndex to i
			end if
		end repeat
		originalItems's item largerItemIndex
	end greaterInteger
end script

repeat with i from 1 to count accellerator's theList
	considering case
		if accellerator's theList's item i is not in accellerator's originalItems then
			set accellerator's originalItems's end to accellerator's theList's item i
			set accellerator's originalItemsCount's end to 1
		else
			set ind to accellerator's indexof(accellerator's theList's item i, accellerator's originalItems)
			set accellerator's originalItemsCount's item ind to ((accellerator's originalItemsCount's item ind) + 1)
		end if
	end considering
end repeat

set f0 to paragraphs 2 thru -2 of return & accellerator's greaterInteger() as text
 end mostPopularItemInList

 -- apply the most popular reading f0 to the original file fileName as a tag
tell application "Finder"

set fileName to fileName as text
set currentFile to (get POSIX path of file fileName)

tell currentFile
	
	my tagCmd(it as text) --	else
end tell

end tell

on tagCmd(f)
    do shell script "/usr/local/bin/tag -a " & "'" & f0 & "'" & space & quoted form of POSIX path of fileName -- "(get selection)" -- posix path convert path with colon to use in shell
end tagCmd

`

It’s a long script so I can’t go through it all just yet but two things come to mind at first glance.

You have some redundancies in the finder tell block. I added an alert in case it tries to work directly with the original wav file. By the way, if you typically get the wav file from the same directory tree, you can add a default location parameter to the choose file command to save a few clicks.

tell application "Finder"
	set passFolder to "HD:Users:me:Desktop:conv:"
	
	set volume alert volume 0
	delete (every item of folder passFolder whose name contains "passing")
	delete (every item of folder passFolder whose name extension is "csv")
	
	set fileName to (choose file with prompt "Please Select a WAV file for Processing")
	set srcCont to container of fileName
	try
		set fileName to duplicate fileName to passFolder without replacing
	end try
	if container of fileName is srcCont then
		display alert "wrong file"
	else
		set name of fileName to "passing.wav"
	end if
end tell

Second, in some of your lines (e.g. set lineCounts and do shell script "cat", set head2) you still have some of my settings in them (e.g. head -n 34). Those values are specific to the example data which I used so you need to adjust them according to your data.

Does your csv have any header or footer rows, and if so, how many are each?

dirt simplest would be to import cvs to excel, then sort row 3

BR

1 Like

Just using liner search you end up with ~n per search. better algorithm get log n. also the breakpoints has a span. i.e. if tone is slightly above key then you end up with wrong note. propose the following instead:

set freqList to {15.9, 16.81, 17.81, 18.81, 19.91, 21.22, 22.48, 23.81, 25.23, 26.72, 28.31, 30.0, 31.79, 33.67, 35.67, 37.79, 40.41, 42.41, 44.94, 47.61, 50.44, 53.44, 56.61, 59.13, 62.66, 66.37, 70.32, 74.5, 78.92, 83.62, 88.59, 93.86, 99.44, 105.35, 111.61, 118.25, 125.28, 132.72, 140.62, 145.98, 157.83, 167.22, 177.16, 187.7, 198.86, 210.68, 223.21, 236.48, 250.54, 265.45, 281.22, 297.94, 315.67, 334.44, 354.32, 375.38, 397.71, 421.35, 446.41, 472.95, 501.07, 530.87, 562.44, 595.88, 631.31, 668.86, 708.63, 750.76, 795.4, 842.7, 892.81, 945.89, 1002.15, 1061.73, 1124.87, 1191.76, 1262.62, 1337.7, 1417.25, 1501.52, 1590.8, 1685.39, 1785.61, 1891.79, 2004.27, 2123.45, 2249.72, 2383.5, 2525.23, 2675.39, 2834.48, 3003.15, 3181.58, 3370.78, 3571.21, 3783.56, 4008.55, 4246.91, 4499.44, 4766.99, 5050.45, 5350.76, 5668.93, 6006.03, 6363.17, 6741.54, 7142.41, 7567.12}
set noteList to {"C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0", "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1", "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3", "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A4#", "B4", "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5", "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6", "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7", "C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8"}
set hi to (count of freqList)
set lo to 0

set y to 17.0 --test value
repeat while true
	set i to ((hi + lo) div 2)
	if i = lo then
		if y ≥ {(item lo of freqList) * 1.012591725} then
			set i to lo + 1
		else
			set i to lo
		end if
		exit repeat
	else if y > item i of freqList then
		set lo to i
	else
		set hi to i
	end if
end repeat
log i & item i of noteList -- i is the key in noteList

this code should be faster and tune in to right key.
BR

@Hallenstal This works well and is much more efficient than my suggestion but its workings are a little obscure. By the way, I don’t really understand this line segment:

{(item lo of freqList) * 1.012591725}

Why multiply at all? And why by that specific number?

It has the downside of making it non-obvious where a frequency falls, e.g. 17.8 Hz is obviously a D0 but now you would have to calculate to determine which note it actually is.

Thanks.

What if search tone lies between 2 notes. Then how to determine which note it is? Since the step between notes are half a note it means each half note step has a constant factor. The multiplication factor in the code is quarter of a note. So. If search tone is within the range of ± quarter of note, then that note is selected.

Ok fixed the frequency list so that cutoff is ± quarter of a note. also there was an error setting the lo to 0, it should have been 1.

set noteList to {"C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0", "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1", "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3", "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4", "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5", "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6", "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7", "C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8"}
set freqList to {15.62865994, 16.55798701, 17.5425722, 18.58571343, 19.69087732, 20.86175261, 22.10225606, 23.41652655, 24.8089434, 26.28416187, 27.84710351, 29.50298203, 31.25731951, 33.11597609, 35.08515233, 37.17142479, 39.38174671, 41.7235073, 44.20452005, 46.83305517, 49.61789681, 52.56833167, 55.69420494, 59.00595405, 62.51462902, 66.23194425, 70.17030259, 74.34284373, 78.76350342, 83.44702252, 88.40904217, 93.66611828, 99.23579777, 105.1366792, 111.3884016, 118.0118722, 125.0292463, 132.4639002, 140.3405935, 148.6857033, 157.527011, 166.8940568, 176.8180643, 187.3322165, 198.4715589, 210.2732791, 222.7768239, 236.0238445, 250.058572, 264.9278004, 280.681187, 297.3714066, 315.054022, 333.7881135, 353.6361287, 374.6644331, 396.9431386, 420.5466375, 445.5536479, 472.0476682, 500.1170439, 529.8555423, 561.3624324, 594.742734, 630.1080232, 667.5761685, 707.2723366, 749.3288869, 793.8863565, 841.093275, 891.1072749, 944.0952572, 1000.234046, 1059.711009, 1122.725182, 1189.485468, 1260.216005, 1335.152178, 1414.544756, 1498.658132, 1587.772913, 1682.186709, 1782.21455, 1888.190473, 2000.468142, 2119.422603, 2245.449571, 2378.970936, 2520.43201, 2670.304357, 2829.089305, 2997.315265, 3175.545033, 3364.373417, 3564.4291, 3776.380946, 4000.936076, 4238.844414, 4490.899142, 4757.941872, 5040.864227, 5340.609506, 5658.178818, 5994.63153, 6351.090652, 6728.746042, 7128.8582, 7552.762099, 8001.872632}
set hi to (count of freqList)
set lo to 1

set y to 4000 --test value
if y < item 1 of freqList or y > item hi of freqList then
	set thenote to "xxxx"
else
	repeat while true
		set i to ((hi + lo) div 2)
		if i = lo then
			exit repeat
		else if y > item i of freqList then
			set lo to i
		else
			set hi to i
		end if
	end repeat
	set thenote to item i of noteList
end if
log thenote

BR

1 Like

Thanks for clarifying. That makes sense and I wondered about it but did not know the math involved. In this case though, I think the original data already takes that into account to an extent. It uses values approximately 1/1.02930223664349 (or 24√2) below each note’s frequency and then does some rounding. So, for example, the frequency of the first note is 16.35 Hz but they used 15.9 Hz as the threshold. Or the A0 not.

FWIW, I got my note frequencies from here.

Edit: Just saw your update. That set lo to 0 did confuse me. It would generate an error if the script ever got to the item lo of freqList but I wasn’t sure at first whether that condition would ever occur.

Hi,
I took the frequency table from wikipedia.
each half note is separated with a factor of 1.059463. so frequency for C0 is 16.35. to get C0# you take 1,059463*16.35 and you get 17.32 which is C#.
to get a quarter note up from C0 you take 16.35*sqrt(1.059463) a quarter down 16.35/sqrt(1.059463)
which brings me to that I used excel to calculate the thresholds using logarithms, but I used log() instead of ln() in my calculation which means the freqList is wrong, it should be:

set freqList to {15.8861017, 16.83073844, 17.8315393, 18.89186471, 20.01523487, 21.20539646, 22.46633288, 23.80225395, 25.21760629, 26.71712466, 28.30581149, 29.98896768, 31.77220293, 33.66147689, 35.6630889, 37.78372941, 40.03045946, 42.41079293, 44.93267604, 47.60450791, 50.43522287, 53.43425962, 56.61162299, 59.97792507, 63.54439556, 67.32294348, 71.32617779, 75.56744853, 80.0609292, 84.82159615, 89.86535209, 95.20902611, 100.8704457, 106.8685398, 113.223246, 119.955809, 127.0887705, 134.6459076, 142.652335, 151.1349177, 160.1218584, 169.6432129, 179.7306836, 190.4180316, 201.7408709, 213.7369767, 226.4464919, 239.9117209, 254.177644, 269.2918151, 285.30467, 302.2698353, 320.2437168, 339.2864258, 359.4613672, 380.8360633, 403.4817418, 427.4740564, 452.8929839, 479.8234417, 508.3551851, 538.5835273, 570.6094429, 604.5395677, 640.4874336, 678.5727486, 718.9228373, 761.6721265, 806.9635865, 854.9481128, 905.7859678, 959.6467805, 1016.71037, 1077.166849, 1141.219298, 1209.079135, 1280.974867, 1357.145291, 1437.845675, 1523.344665, 1613.927379, 1709.896431, 1811.571936, 1919.293561, 2033.420534, 2154.334727, 2282.437566, 2418.158271, 2561.949735, 2714.290583, 2875.691349, 3046.6883, 3227.853728, 3419.792863, 3623.143871, 3838.587122, 4066.841069, 4308.668424, 4564.875132, 4836.316541, 5123.899469, 5428.582195, 5751.382698, 6093.37763, 6455.708486, 6839.584696, 7246.287742, 7677.174244, 8133.683167}

BR

Hallenstal, bless you man. My original freqList data is accurate to 2 decimal places. Each item is the frequency in hz of the associated noteList item - 50 cents (1/4 of a tone) there’s a margin of error in cents for the first few items but wouldn’t really have much of an impact on the audio’s behaviour in relativity to the harmonic environment to which its introduced.

The app has its flaws, if I were to use 2 audio files; a perfect F2 and another with a tag that’s a perfect C3, the interval is a perfect 5th, the combination of the two frequencies would resonate well and complement each other harmonically. In this script rounding to the nearest 1/4 tone, whilst its only being used to roughly sort short percussive audio samples; if I chose two files one with an F2 tag and one with a C3 tag. The F2 selection could be F2 -29 cents and the C3 - C3 +49 cents. It will impart a very different result sonically, there will be less resonance and it may sound dissonant. However, these anomalies do have an application and as most of the instruments I use are tuned to 440hz concert pitch I decided to take the lucky dip option and rather than write to a selection of perfectly tuned percussion samples see what the characteristics of roughly tuned samples impart in the sonic environment of the compositions they’re used in, though with a bank of 30,000 percussion samples Im sure there will be something that does the job.

I plan to write a script with a dialog box / table that lists kit pieces (searchable by contents of a tag) then on each kit piece row create a checkbox list for multiple selections of each interval of the 12 note scale. Here’s an example of the script for all intervals matched to a chosen key that I compiled with help from Nigel Garvey, Peavine and a few other members, I’ve just made some additions with help from both of your suggestions

`use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"


property twelfth_root_2 : (2 ^ (1 / 12))

set fRoot to text returned of (display dialog "Choose fundamental frequency in Hertz" default answer "")

-- I'll change this to a list
display dialog "Choose fundemental frequency in Hertz" default answer ""
if text returned of result contains "A" then
	set fRoot to 440.0
else if text returned of result contains "B" then
	set fRoot to 493.88
else if text returned of result is "Bb" then
	set fRoot to 466.16
else if text returned of result contains "C" then
	set fRoot to 523.25
else if text returned of result contains "Ab" then
	set fRoot to 415.3
else if text returned of result contains "G" then
	set fRoot to 392.0
else if text returned of result contains "Gb" then
	set fRoot to 369.99
else if text returned of result contains "F" then
	set fRoot to 349.23
else if text returned of result contains "E" then
	set fRoot to 329.63
else if text returned of result contains "Eb" then
	set fRoot to 311.13
else if text returned of result contains "D" then
	set fRoot to 293.66
else if text returned of result contains "Db" then
	set fRoot to 277.18
else
	set fRoot to text returned of result
end if



set presets to {"b9", "9th", "m3", "3rd", "4th", "Dim5", "5th", "Aug5", "6th", "7th", "m7", "Oct"}
set theMode to (choose from list presets with prompt "Choose a character:" with multiple selections allowed)
if (theMode is false) then return -- user cancelled

set frequencies to {}
repeat with semitones from 1 to (count presets)
	if (theMode contains {item semitones of presets}) then
		set frequency to (fRoot * (twelfth_root_2 ^ semitones))
		set end of frequencies to ¬
			frequency --  frequency / 2, frequency * 2, frequency * 4, frequency * 8, frequency * 16, frequency * 32, frequency * 64}
	end repeat

log frequencies

set theNotes to {}
set freqList to {15.9, 16.81, 17.81, 18.81, 19.91, 21.22, 22.48, 23.81, 25.23, 26.72, 28.31, 30.0, 31.79, 33.67, 35.67, 37.79, 40.41, 42.41, 44.94, 47.61, 50.44, 53.44, 56.61, 59.13, 62.66, 66.37, 70.32, 74.5, 78.92, 83.62, 88.59, 93.86, 99.44, 105.35, 111.61, 118.25, 125.28, 132.72, 140.62, 145.98, 157.83, 167.22, 177.16, 187.7, 198.86, 210.68, 223.21, 236.48, 250.54, 265.45, 281.22, 297.94, 315.67, 334.44, 354.32, 375.38, 397.71, 421.35, 446.41, 472.95, 501.07, 530.87, 562.44, 595.88, 631.31, 668.86, 708.63, 750.76, 795.4, 842.7, 892.81, 945.89, 1002.15, 1061.73, 1124.87, 1191.76, 1262.62, 1337.7, 1417.25, 1501.52, 1590.8, 1685.39, 1785.61, 1891.79, 2004.27, 2123.45, 2249.72, 2383.5, 2525.23, 2675.39, 2834.48, 3003.15, 3181.58, 3370.78, 3571.21, 3783.56, 4008.55, 4246.91, 4499.44, 4766.99, 5050.45, 5350.76, 5668.93, 6006.03, 6363.17, 6741.54, 7142.41, 7567.12}
set noteList to {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}

set oldDelims to text item delimiters
set text item delimiters to ","
repeat with a_reading in frequencies
	set y to (contents of a_reading)
	-- set y to y as integer
	set hi to (count of freqList)
	set lo to 1
	if y < item 1 of freqList or y > item hi of freqList then
		set thenote to "xxxx"
	else
		repeat while true
			set i to ((hi + lo) div 2)
			if i = lo then
				exit repeat
			else if y > item i of freqList then
				set lo to i
			else
				set hi to i
			end if
		end repeat
		set thenote to item i of noteList
	end if
	log thenote
	set y to thenote as text
	set end of theNotes to y
end repeat
return theNotes

   then a search by tag for files with tags matching the returned items of theNotes

Not sure how to create the dialog box or table for multiple categories all with multiple options, will check out the dialog box toolkit and myriad tables for some inspiration. Though any pointers welcome as I’d probably struggle even with Shane’s complex example scripts I could easily hash something up in Xcode (if they’ve fixed that issue with connecting the UI elements to the delegate script) but aside of not having enough HD space to actually download Xcode, I’d like to take a shot at doing it all in SD