sonifyGSR.pl

#    SonifyGSR
#    Copyright (C) 2022 Cliff Hammett
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <https://www.gnu.org/licenses/>.

#################### META ###############################################################################
#   Title                   :   sonifyGSR.pl                                                            #
#   Purpose                 :   creates a really simple sonification of GSR data to accompany the       #
#                               bio|audio maps of bat walks                                             #
#################### PREQUISITES ########################################################################
# perl                      :   tested on perl v5.26.1. Should run on other versions!                   #
# chuck                     :   the Chuck music programming language https://chuck.cs.princeton.edu/    #
# sox                       :   Sound eXchange http://sox.sourceforge.net/                              #
#                                                                                                       #
# This script was written for operating on linux systems. It will probably run on Macs. It may need     #
# some modification to run on windows                                                                   #
#                                                                                                       #
#################### USAGE ##############################################################################
# perl sonifyGSR.pl <raw data filename> <processed data filename> <target output directory>             #
#                                                                                                       #
# raw data filename         :   original CSV output from location|stimulation censor                    #
# processed data filename   :   processed CSV file created by peak SS                                   #
# target directory          :   directory where the files will be outputted. Top level directory does   #
#                               not need to exist yet, rest of path does                                #
#                                                                                                       #
#########################################################################################################

use strict;
use warnings;
    
my $dataFileIn = $ARGV[0];
my $structureFileIn = $ARGV[1];
my $dirOut = $ARGV[2];
my $freqMin = 100;
my $freqMax = 1000;

my $rah_data = &processCSV($dataFileIn);
my $rah_structure = &processCSV($structureFileIn);
my $range = &findMinMaxValue($rah_data, "sens");
&printHash($rah_structure->[0]);

system "mkdir $dirOut";
system "mkdir $dirOut/raw";
system "mkdir $dirOut/raw/compiled";

my $k = 0;
my $size = @{$rah_data};
my $soxline = "";
my %snd;
my $c=0;
my $f=0;
for (my $i=0; $i+1<$size; $i++){
    my $rd = $rah_data->[$i];
#    &printHash($rd);
    my $rs = $rah_structure->[$k];
#    &printHash($rs);
    if ($rd->{millis} > $rs->{millisStart}){
        my $freqFract = (1 + $rd->{sens} - $range->{min}) / $range->{max};
        my $freqOut = ($freqFract * ($freqMax - $freqMin)) + $freqMin;
        my $dur = $rah_data->[$i+1]->{millis} - $rd->{millis};
        my $file = "$rs->{id}.mp3";
#        print "emit $freqOut to $file for $dur milliseconds\n";
        my $fileOut; 
        my $s = "$freqOut-$dur";
        if ($snd{$s}){
            $fileOut = $snd{$s};
        }else{
            $fileOut = "$dirOut/raw/$rs->{id}-$f.wav";
            my $chuckcmd = "chuck ck/fm2:$dur:$freqOut:50:50 ck/rec-auto-stereo:$dur:$fileOut";
            print "$chuckcmd\n";
            system $chuckcmd;
            $snd{"$freqOut-$dur"} = $fileOut;
        }
        $soxline .= "$fileOut ";
        $f++;
#        push @sox, $fileOut;
        if ($c > 1000){
            print "compiling at $f\n";
            &compileFile($soxline, $rs->{id});
            $soxline = "";
            $c=0;
            $f=0;
        }
        $c++;
    }
    if ($rd->{millis} >= $rs->{millisEnd} || $i+2 == $size){
        $k++;
        print "compiling at $f\n";
        &compileFile($soxline, $rs->{id});
        $soxline = "";
        $f=0;
        $c=0;
    }
}


sub compileFile{
    my ($soxline, $id) = @_;
    print "compiling $id\n";
    my $destFile = "$dirOut/$id.wav";
    my $compfile = "$dirOut/raw/compiled/$id.wav";
    if (-e $destFile){
        system "sox $destFile $soxline $compfile";            
        system "cp $compfile $destFile";
    }else{
        system "sox $soxline $destFile";
    }
}

sub findMinMaxValue{
    my ($rah, $key) = @_;
    my $size = @{$rah};
    my $rtn->{min} = 1024;
    $rtn->{max} = 0;
    for(my $i=0; $i<$size; $i++){
        my $val = $rah->[$i]->{$key};
        if ($val > $rtn->{max}){
            $rtn->{max} = $val;
        }
        if($val < $rtn->{min}){
            $rtn->{min} = $val;
        }
    }
    return $rtn;
}


sub processCSV{
    my $file = shift;         
    open DATA, '<', $file or die "file didn't work?!\n";                                                                                           
    my @data = <DATA>;        
    my $datasize = @data;
    chomp $data[0];           
    my @header = split(",", $data[0]);
    my $headersize = @header;
    my $ra_data;
    for (my $i=0; $i<$datasize; $i++){ 
        chomp $data[$i];
#        if ($csv->parse($data[$i])){     
           # print $data[$i] . "\n";       
            my @value = split(",", $data[$i]);
            my $rh_line;
            for (my $k=0; $k<$headersize; $k++){
               # print "$k - " . $header[$k] . ": " . $value[$k] . "\n"; 
                $rh_line->{$header[$k]} = $value[$k];                                                                                              
            }
            push @{$ra_data}, $rh_line;
 #       }else{print 'error in processing';}                                                                                                       
    }
    close (DATA);
    return $ra_data;
}

sub printHash{
    my $rh = shift;
    foreach my $key (keys %{$rh}) {
        my $value = $rh->{$key};       
        print "$key : $value,";        
    }
    print "\n";
}