Audio overlap loop: Difference between revisions

From Rosetta Code
Content added Content deleted
(Added Wren)
Line 367: Line 367:
make_gui
make_gui
</lang>
</lang>

=={{header|Wren}}==
{{trans|Go}}
The ability to call external processes such as ''SoX'' is expected to be added to Wren-cli in the next release. In the meantime, we embed the following Wren script in a C host to complete this task.
<lang ecmascript>/* audio_overlap_loop.wren */

class C {
foreign static getInput(maxSize)

foreign static play(args)
}

var fileName = "loop.wav"

var reps = 0
while (!reps || !reps.isInteger || reps < 1 || reps > 6) {
System.write("Enter number of repetitions (1 to 6) : ")
reps = Num.fromString(C.getInput(1))
}

var delay = 0
while (!delay || !delay.isInteger || delay < 50 || delay > 500) {
System.write("Enter delay between repetitions in microseconds (50 to 500) : ")
delay = Num.fromString(C.getInput(3))
}

var decay = 0
while (!decay || decay < 0.2 || decay > 0.9) {
System.write("Enter decay between repetitions (0.2 to 0.9) : ")
decay = Num.fromString(C.getInput(5))
}

var args = [fileName, "-V1", "echo", "0.8", "0.7"]
var decay2 = 1
for (i in 1..reps) {
var delayS = (i * delay).toString
decay2 = decay2 * decay
var decayS = decay2.toString
args.add(delayS)
args.add(decayS)
}
C.play(args.join(" "))</lang>
<br>
We now embed this in the following C program, compile and run it.
<lang c>#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
#include "wren.h"

void C_getInput(WrenVM* vm) {
int maxSize = (int)wrenGetSlotDouble(vm, 1) + 2;
char input[maxSize];
fgets(input, maxSize, stdin);
__fpurge(stdin);
input[strcspn(input, "\n")] = 0;
wrenSetSlotString(vm, 0, (const char*)input);
}

void C_play(WrenVM* vm) {
const char *args = wrenGetSlotString(vm, 1);
char command[strlen(args) + 5];
strcpy(command, "play ");
strcat(command, args);
system(command);
}

WrenForeignMethodFn bindForeignMethod(
WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature) {
if (strcmp(module, "main") == 0) {
if (strcmp(className, "C") == 0) {
if (isStatic && strcmp(signature, "getInput(_)") == 0) return C_getInput;
if (isStatic && strcmp(signature, "play(_)") == 0) return C_play;
}
}
return NULL;
}

static void writeFn(WrenVM* vm, const char* text) {
printf("%s", text);
}

void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
switch (errorType) {
case WREN_ERROR_COMPILE:
printf("[%s line %d] [Error] %s\n", module, line, msg);
break;
case WREN_ERROR_STACK_TRACE:
printf("[%s line %d] in %s\n", module, line, msg);
break;
case WREN_ERROR_RUNTIME:
printf("[Runtime Error] %s\n", msg);
break;
}
}

char *readFile(const char *fileName) {
FILE *f = fopen(fileName, "r");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
rewind(f);
char *script = malloc(fsize + 1);
fread(script, 1, fsize, f);
fclose(f);
script[fsize] = 0;
return script;
}

int main(int argc, char **argv) {
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
config.errorFn = &errorFn;
config.bindForeignMethodFn = &bindForeignMethod;
WrenVM* vm = wrenNewVM(&config);
const char* module = "main";
const char* fileName = "audio_overlap_loop.wren";
char *script = readFile(fileName);
WrenInterpretResult result = wrenInterpret(vm, module, script);
switch (result) {
case WREN_RESULT_COMPILE_ERROR:
printf("Compile Error!\n");
break;
case WREN_RESULT_RUNTIME_ERROR:
printf("Runtime Error!\n");
break;
case WREN_RESULT_SUCCESS:
break;
}
wrenFreeVM(vm);
free(script);
return 0;
}</lang>


{{omit from|Bc}}
{{omit from|Bc}}

Revision as of 09:01, 25 September 2021

Audio overlap loop is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Audio Overlap Loop is a program that produces an "echo chamber" effect by playing an audio file several times in an overlapping loop. A repetition level determines the number of times that the file is looped. For the purpose of this task, write a program that takes a parameter for the number of repetitions and plays the file loop.wav in an overlapping loop according to the number of repetitions.

Optionally take parameters for delay between repetitions, and decay (fractional volume reduction between consecutive repetitions).

Delphi

<lang Delphi> program Audio_Overlap_Loop;

{$APPTYPE CONSOLE}

uses

 System.SysUtils,
 Winapi.Windows,
 Winapi.MMSystem;

function QueryIntNumber(Msg: string; min, max: Integer): Integer; var

 val: string;

begin

 Result := 0;
 repeat
   Writeln(Msg);
   Readln(val);
   if not TryStrToInt(val, Result) then
   begin
     Writeln('"', val, '" is not a valid number.');
     Continue;
   end;
   if Result < min then
   begin
     Writeln('"', val, '" must be greater then ', min);
     Continue;
   end;
   if (Result > max) then
   begin
     Writeln('"', val, '" must be lower then ', max);
     Continue;
   end;
   Break;
 until True;

end;

function QueryFloatNumber(Msg: string; min, max: double): double; var

 val: string;

begin

 Result := 0;
 repeat
   Writeln(Msg);
   Readln(val);
   // acept ',' ou '.' as decimal separator
   val := val.Replace(',',FormatSettings.DecimalSeparator);
   val := val.Replace('.',FormatSettings.DecimalSeparator);
   if not TryStrToFloat(val, Result) then
   begin
     Writeln('"', val, '" is not a valid number.');
     Continue;
   end;
   if Result < min then
   begin
     Writeln('"', val, '" must be greater then ', min);
     Continue;
   end;
   if (Result > max) then
   begin
     Writeln('"', val, '" must be lower then ', max);
     Continue;
   end;
   Break;
 until True;

end;

procedure SetWaveVolume(Volume: Double); var

 Caps: TWAVEOUTCAPS;
 aVolume: Cardinal;
 VolumeCanal: Word;

begin

 VolumeCanal := Trunc(Volume * word(-1) / 100.0);
 aVolume := MakeLong(VolumeCanal, VolumeCanal);
 if WaveOutGetDevCaps(WAVE_MAPPER, @Caps, sizeof(Caps)) = MMSYSERR_NOERROR then
   if Caps.dwSupport and WAVECAPS_VOLUME = WAVECAPS_VOLUME then
     WaveOutSetVolume(WAVE_MAPPER, aVolume);

end;

procedure Play(music: string; volume: Double = 100.0; delayMs: Cardinal = 0); begin

 SetWaveVolume(volume);
 PlaySound(Pchar(music), SND_SYNC, 0);
 if delayMs > 0 then
   Sleep(delayMs);

end;

var

 vol: Double = 100.0;
 i: Integer;
 rep: Integer;
 delay: DWORD;
 decay: Double;

begin

 rep := QueryIntNumber('Enter number of repetitions (1 to 20) : ', 1, 20);
 delay := QueryIntNumber('Enter delay between repetitions in milliseconds (0 to 1000) : ',
   0, 1000);
 decay := QueryFloatNumber('Enter decay between repetitions (0.01 to 0.99) : ',
   0.01, 0.99);
 for i := 1 to rep do
 begin
   Play('echo.wav', vol, delay);
   vol := vol * (1 - decay);
 end;

end.</lang>

Output:
Enter number of repetitions (1 to 20) :
5
Enter delay between repetitions in milliseconds (0 to 1000) :
0
Enter decay between repetitions (0.01 to 0.99) :
0.2

Go

As Go does not have any audio support in its standard library, this invokes the SoX utility's 'play' command with the appropriate parameters to achieve the 'echo chamber' effect. <lang go>package main

import (

   "bufio"
   "fmt"
   "log"
   "os"
   "os/exec"
   "strconv"

)

func check(err error) {

   if err != nil {
       log.Fatal(err)
   }

}

func main() {

   fileName := "loop.wav"
   scanner := bufio.NewScanner(os.Stdin)
   reps := 0
   for reps < 1 || reps > 6 {
       fmt.Print("Enter number of repetitions (1 to 6) : ")
       scanner.Scan()
       input := scanner.Text()
       check(scanner.Err())
       reps, _ = strconv.Atoi(input)
   }
   delay := 0
   for delay < 50 || delay > 500 {
       fmt.Print("Enter delay between repetitions in microseconds (50 to 500) : ")
       scanner.Scan()
       input := scanner.Text()
       check(scanner.Err())
       delay, _ = strconv.Atoi(input)
   }
   decay := 0.0
   for decay < 0.2 || decay > 0.9 {
       fmt.Print("Enter decay between repetitions (0.2 to 0.9) : ")
       scanner.Scan()
       input := scanner.Text()
       check(scanner.Err())
       decay, _ = strconv.ParseFloat(input, 64)
   }
   args := []string{fileName, "echo", "0.8", "0.7"}
   decay2 := 1.0    
   for i := 1; i <= reps; i++ {
       delayStr := strconv.Itoa(i * delay)
       decay2 *= decay
       decayStr := strconv.FormatFloat(decay2, 'f', -1, 64)        
       args = append(args, delayStr, decayStr)        
   }
   cmd := exec.Command("play", args...)    
   err := cmd.Run()
   check(err)

}</lang>

JavaScript/HTML

<lang JavaScript><script> var j = prompt("Enter the sound manipulation level you want", ""); for(i=0; i<j; i++) {

   document.write("<bgsound src='loop.wav'>")

} </script></lang>


Julia

Uses Julia's ability to run iterations of a 4 loop in separate threads to play a file 4 times, with each play 0.1 seconds out of sync with the previous play. Requires available threads on the CPU at Julia startup. <lang julia>const soundfile = "loop.wav"

if length(ARGS) < 1

   println("Usage: give number of repetitions in echo effect as argument to the program.")

else

   ((repetitions = tryparse(Int, ARGS[1])) != nothing) || (repetitions = 3)
   (repetitions < 1) && (repetitions = 3)
   (repetitions > Threads.nthreads()) && (repetitions = Threads.nthreads())
   @Threads.threads for secs in 0.0:0.1:((repetitions - 1) * 0.1)
       begin sleep(secs); run(`play "$soundfile"`); end
   end

end </lang>

Nim

Translation of: Go

As in Go version, we use Sox "play" command. <lang Nim>import osproc, strutils

proc getValue[T: int | float](msg: string; minval, maxval: T): T =

 while true:
   stdout.write msg
   stdout.flushFile()
   try:
     result = when T is int: stdin.readLine.strip().parseInt()
              else: stdin.readLine.strip().parseFloat()
     if result notin minval..maxval:
       echo "Invalid value"
     else:
       return
   except ValueError:
     echo "Error: invalid value."
   except EOFError:
     echo()
     quit "Quitting.", QuitFailure

const FileName = "loop.wav" let reps = getValue("Enter number of repetitions (1 to 6): ", 1, 6) let delay = getValue("Enter delay between repetitions in microseconds (50 to 500): ", 50, 500) let decay = getValue("Enter decay between repetitions (0.2 to 0.9): ", 0.2, 0.9)

var args = @[FileName, "echo", "0.8", "0.7"] var decay2 = 1.0 for i in 1..reps:

 decay2 *= decay
 args.add $(i * delay)
 args.add $decay2

echo execProcess("play", args = args, options = {poStdErrToStdOut, poUsePath})</lang>

Tcl

Using the popular snack (wiki) extension for audio support, the following presents a GUI to control echo production.

This uses a couple of interesting Tcl features:

  • GUI widgets bound to global variables
  • playback runs in a coroutine to provide asynchrony with yield
  • lock_play provides a "transaction"-style control structure similar to "with" in [Python] or (with-*) in [Lisp]

As a bonus, two playback methods are provided - run and mix, which exercise different capabilities of snack (playing multiple sounds at once vs programmable filters). Notice that run disabled buttons only until the last echo has started, while mix does so until the entire playback (cropped to sound level 0) is completed. Either of these may be desirable in different circumstances, so both are left as an example.

<lang Tcl>package require Tk package require snack


  1. variables bound to GUI:

set filename "sample.wav" set nreps 5 set delay 200 set decay 0.9


  1. initial snack objects:

snack::sound wav -load sample.wav snack::sound mixed  ;# used by [run] snack::sound out  ;# used by [mix]

snack::sound hush -rate [wav cget -rate] -channels [wav cget -channels] hush length [wav length]


proc make_gui {} {

   grid [label .l0 -text "Filename:"] [button .b0 -textvariable ::filename -command choose_file] -sticky nsew
   grid [label .l1 -text "Repetitions"] [entry .e1 -textvariable ::nreps] -sticky nsew
   grid [label .l2 -text "Pause"] [entry .e2 -textvariable ::delay] -sticky nsew
   grid [label .l3 -text "Decay"] [entry .e3 -textvariable ::decay] -sticky nsew
   grid [frame .b] -   ;# "-" for colspan
   grid [
       button .b.run  -text "Play" -command {coroutine runner run}
   ] [
       button .b.mix  -text "Premix" -command {coroutine runner mix}
   ] [
       button .b.stop -text "Stop" -command stop -state disabled
   ] [
       button .b.exit -text "Exit" -command exit
   ] -sticky nsew

}

  1. snack wraps tk_getOpenFile with suitable options to load supported audio files

proc choose_file {} {

   global filename
   set fn [snack::getOpenFile -initialfile $filename]
   if {$fn eq ""} return
   wav read [set filename $fn]

}

  1. disable play and enable stop for the duration of $script

proc lock_play {script} {

   .b.run configure -state disabled
   .b.mix configure -state disabled
   .b.stop configure -state normal
   try {
       uplevel 1 $script
   } finally {
       .b.run configure -state normal
       .b.mix configure -state normal
       .b.stop configure -state disabled
   }

}

  1. play by starting each echo as a distinct sound

proc run {} {

   global nreps delay decay
   lock_play {
       mixed copy wav
       mixed play
       for {set i 1} {$i < $nreps} {incr i} {
           yieldto after $delay [list catch [info coroutine]]  ;# delay without blocking the event loop
                                                               ;# [catch] in case the coroutine has been killed
           mixed mix hush -prescaling $decay   ;# scale and mix with silence to fade
           mixed play
       }
   }

}

  1. play using snack::filter to create the echo

proc mix {} {

   global nreps delay decay
   lock_play {
       out copy wav
       set args {} ;# for snack::filter echo
       for {set i 1} {$i < $nreps} {incr i} {
           lappend args [expr {$delay * $i}] [expr {$decay ** $i}]
       }
       set filter [snack::filter echo 1 1 {*}$args]
       out filter $filter
       $filter destroy
       yieldto out play -command [info coroutine]  ;# return to this proc only when playback completed
   }

}

  1. stop playback

proc stop {} {

   lock_play {
       foreach s {wav mixed out} {
           $s stop     ;# stop all sounds that may be playing
           catch {rename runner {}}    ;# kill the coroutine if it exists
       }
   }

}

make_gui </lang>

Wren

Translation of: Go

The ability to call external processes such as SoX is expected to be added to Wren-cli in the next release. In the meantime, we embed the following Wren script in a C host to complete this task. <lang ecmascript>/* audio_overlap_loop.wren */

class C {

   foreign static getInput(maxSize)
   foreign static play(args)

}

var fileName = "loop.wav"

var reps = 0 while (!reps || !reps.isInteger || reps < 1 || reps > 6) {

   System.write("Enter number of repetitions (1 to 6) : ")
   reps = Num.fromString(C.getInput(1))

}

var delay = 0 while (!delay || !delay.isInteger || delay < 50 || delay > 500) {

   System.write("Enter delay between repetitions in microseconds (50 to 500) : ")
   delay = Num.fromString(C.getInput(3))

}

var decay = 0 while (!decay || decay < 0.2 || decay > 0.9) {

   System.write("Enter decay between repetitions (0.2 to 0.9) : ")
   decay = Num.fromString(C.getInput(5))

}

var args = [fileName, "-V1", "echo", "0.8", "0.7"] var decay2 = 1 for (i in 1..reps) {

   var delayS = (i * delay).toString
   decay2 = decay2 * decay
   var decayS = decay2.toString
   args.add(delayS)
   args.add(decayS)

} C.play(args.join(" "))</lang>
We now embed this in the following C program, compile and run it. <lang c>#include <stdio.h>

  1. include <stdio_ext.h>
  2. include <stdlib.h>
  3. include <string.h>
  4. include "wren.h"

void C_getInput(WrenVM* vm) {

   int maxSize = (int)wrenGetSlotDouble(vm, 1) + 2;
   char input[maxSize];
   fgets(input, maxSize, stdin);
   __fpurge(stdin);
   input[strcspn(input, "\n")] = 0;
   wrenSetSlotString(vm, 0, (const char*)input);

}

void C_play(WrenVM* vm) {

   const char *args = wrenGetSlotString(vm, 1);
   char command[strlen(args) + 5];
   strcpy(command, "play ");
   strcat(command, args);
   system(command);

}

WrenForeignMethodFn bindForeignMethod(

   WrenVM* vm,
   const char* module,
   const char* className,
   bool isStatic,
   const char* signature) {
   if (strcmp(module, "main") == 0) {
       if (strcmp(className, "C") == 0) {
           if (isStatic && strcmp(signature, "getInput(_)") == 0) return C_getInput;
           if (isStatic && strcmp(signature, "play(_)") == 0) return C_play;
       }
   }
   return NULL;

}

static void writeFn(WrenVM* vm, const char* text) {

   printf("%s", text);

}

void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {

   switch (errorType) {
       case WREN_ERROR_COMPILE:
           printf("[%s line %d] [Error] %s\n", module, line, msg);
           break;
       case WREN_ERROR_STACK_TRACE:
           printf("[%s line %d] in %s\n", module, line, msg);
           break;
       case WREN_ERROR_RUNTIME:
           printf("[Runtime Error] %s\n", msg);
           break;
   }

}

char *readFile(const char *fileName) {

   FILE *f = fopen(fileName, "r");
   fseek(f, 0, SEEK_END);
   long fsize = ftell(f);
   rewind(f);
   char *script = malloc(fsize + 1);
   fread(script, 1, fsize, f);
   fclose(f);
   script[fsize] = 0;
   return script;

}

int main(int argc, char **argv) {

   WrenConfiguration config;
   wrenInitConfiguration(&config);
   config.writeFn = &writeFn;
   config.errorFn = &errorFn;
   config.bindForeignMethodFn = &bindForeignMethod;
   WrenVM* vm = wrenNewVM(&config);
   const char* module = "main";
   const char* fileName = "audio_overlap_loop.wren";
   char *script = readFile(fileName);
   WrenInterpretResult result = wrenInterpret(vm, module, script);
   switch (result) {
       case WREN_RESULT_COMPILE_ERROR:
           printf("Compile Error!\n");
           break;
       case WREN_RESULT_RUNTIME_ERROR:
           printf("Runtime Error!\n");
           break;
       case WREN_RESULT_SUCCESS:
           break;
   }
   wrenFreeVM(vm);
   free(script);
   return 0;

}</lang>