Patch title: Release 92 bulk changes
Abstract:
File: /linux/multimedia/sound.pli
Key:
    Removed line
    Added line
   
module "/pliant/admin/file.pli"
module "/pliant/language/unsafe.pli"
module "/pliant/language/stream.pli"
module "/pliant/admin/file.pli"
module "/pliant/language/unsafe.pli"
module "/pliant/language/stream.pli"
module "/pliant/language/stream/pipe.pli"
module "/pliant/language/os.pli"
module "/pliant/admin/execute.pli"
module "/pliant/language/os.pli"
module "/pliant/admin/execute.pli"
module "/pliant/language/stream/pipe.pli"


module "/pliant/language/stream/listmode.pli"
module "/pliant/language/stream/openmode.pli"
module "/pliant/language/stream/multi.pli"
module "/pliant/language/stream/filesystembase.pli"


constant os_SNDCTL_DSP_RESET 0C0045000h
constant os_SNDCTL_DSP_SYNC 0C0045001h
constant os_SNDCTL_DSP_SPEED 0C0045002h
constant os_SNDCTL_DSP_STEREO 0C0045003h
constant os_SNDCTL_DSP_SETFMT 0C0045005h
constant os_SNDCTL_DSP_CHANNELS 0C0045006h
constant os_SNDCTL_DSP_GETOSPACE 8010500Ch
constant os_SNDCTL_DSP_GETISPACE 8010500Dh
constant os_SNDCTL_DSP_GETODELAY 80045017h
constant alsa (file_query "file:/lib/libasound.so.2" standard)=defined or (file_query "file:/usr/lib/libasound.so.2" standard)=defined
constant recover true
constant recover2 true
constant resample true
constant timeout 5


type os_count_info
  field Int bytes
  field Int blocs
  field Int ptr


#----------------------------------------------------------------------------
#  OS interface


function sound_open dsp options
  arg_rw Stream dsp ; arg Str options
  if (options option "reset")
    os_ioctl dsp:stream_handle os_SNDCTL_DSP_RESET null
  var Int stereo := shunt (options option "mono") 0 1
  if (os_ioctl dsp:stream_handle os_SNDCTL_DSP_STEREO addres
    console "failed to set audio mono/stereo" eol
  var Int rate := options option "rate" Int 44100
  if (os_ioctl dsp:stream_handle os_SNDCTL_DSP_SPEED address
    console "failed to set audio sampling rate" eol
  var Int bits := options option "bits" Int 16
  if (os_ioctl dsp:stream_handle os_SNDCTL_DSP_SETFMT addres
    console "failed to set audio sampling resolution" eol


function sound_close dsp options
  arg_rw Stream dsp ; arg Str options
  if not (options option "nosync")
    os_ioctl dsp:stream_handle os_SNDCTL_DSP_SYNC null
if alsa
  # http://www.alsa-project.org/
  # http://equalarea.com/paul/alsa-audio.html


  function snd_pcm_hw_params_sizeof -> s
    arg Int s
    external "libasound.so.2" "snd_pcm_hw_params_sizeof"


  function snd_pcm_open pcm name stream mode -> err
    arg_rw Address pcm ; arg CStr name ; arg Int stream mode err
    external "libasound.so.2" "snd_pcm_open"
  constant SND_PCM_STREAM_PLAYBACK 0
  constant SND_PCM_STREAM_CAPTURE 1
  constant SND_PCM_NONBLOCK 1

  function snd_pcm_hw_params_malloc ptr -> err
    arg_rw Address ptr ; arg Int err
    external "libasound.so.2" "snd_pcm_hw_params_malloc"

  function snd_pcm_hw_params_any pcm hw -> err
    arg Address pcm hw ; arg Int err
    external "libasound.so.2" "snd_pcm_hw_params_any"

  function snd_pcm_hw_params_set_access pcm hw access -> err
    arg Address pcm hw ; arg Int access err
    external "libasound.so.2" "snd_pcm_hw_params_set_access"
  constant SND_PCM_ACCESS_RW_INTERLEAVED 3

  function snd_pcm_hw_params_set_format pcm hw format -> err
    arg Address pcm hw ; arg Int format err
    external "libasound.so.2" "snd_pcm_hw_params_set_format"
  constant SND_PCM_FORMAT_S8 0
  constant SND_PCM_FORMAT_S16_LE 2
  constant SND_PCM_FORMAT_S24_3LE 32

  function snd_pcm_hw_params_set_rate pcm hw rate dir -> err
    arg Address pcm hw ; arg Int rate dir err
    external "libasound.so.2" "snd_pcm_hw_params_set_rate"

  function snd_pcm_hw_params_set_rate_near pcm hw rate dir -> err
    arg Address pcm hw ; arg_rw Int rate ; arg Int dir err
    external "libasound.so.2" "snd_pcm_hw_params_set_rate_near"

  function snd_pcm_hw_params_set_channels pcm hw channels -> err
    arg Address pcm hw ; arg Int channels err
    external "libasound.so.2" "snd_pcm_hw_params_set_channels"

  function snd_pcm_hw_params pcm hw -> err
    arg Address pcm hw ; arg Int err
    external "libasound.so.2" "snd_pcm_hw_params"

  function snd_pcm_hw_params_free hw
    arg Address hw
    external "libasound.so.2" "snd_pcm_hw_params_free"

  function snd_pcm_prepare pcm -> err
    arg Address pcm ; arg Int err
    external "libasound.so.2" "snd_pcm_prepare"

  function snd_pcm_readi pcm buffer size -> err
    arg Address pcm ; arg Address buffer ; arg Int size err
    external "libasound.so.2" "snd_pcm_readi"

  function snd_pcm_writei pcm buffer size -> err
    arg Address pcm ; arg Address buffer ; arg Int size err
    external "libasound.so.2" "snd_pcm_writei"

  function snd_pcm_status_get_delay pcm -> frames
    arg Address pcm ; arg Int frames
    external "libasound.so.2" "snd_pcm_status_get_delay"

  function snd_pcm_drain pcm -> err
    arg Address pcm ; arg Int err
    external "libasound.so.2" "snd_pcm_drain"

  function snd_pcm_drop pcm -> err
    arg Address pcm ; arg Int err
    external "libasound.so.2" "snd_pcm_drop"

  function snd_pcm_close pcm -> err
    arg Address pcm ; arg Int err
    external "libasound.so.2" "snd_pcm_close"

else

  constant os_SNDCTL_DSP_RESET 0C0045000h
  constant os_SNDCTL_DSP_SYNC 0C0045001h
  constant os_SNDCTL_DSP_SPEED 0C0045002h
  constant os_SNDCTL_DSP_STEREO 0C0045003h
  constant os_SNDCTL_DSP_SETFMT 0C0045005h
  constant os_SNDCTL_DSP_CHANNELS 0C0045006h
  constant os_SNDCTL_DSP_GETODELAY 80045017h
  
  type os_count_info
    field Int bytes
    field Int blocs
    field Int ptr


#----------------------------------------------------------------------------
#  WAV encoding


type WavSignature
  field (Array Char 4) riff
  field uInt32_li length
  field (Array Char 4) wave
 
type WavFormat
  field (Array Char 4) fmt_
  field uInt32_li length
  field uInt16_li version
  field uInt16_li channel
  field uInt32_li rate
  field uInt32_li bytes_per_second
  field uInt16_li bytes_per_sample
  field uInt16_li bits_per_sample
 
type WavChunk
  field (Array Char 4) data
  field uInt32_li length

type WavSignature
  field (Array Char 4) riff
  field uInt32_li length
  field (Array Char 4) wave
 
type WavFormat
  field (Array Char 4) fmt_
  field uInt32_li length
  field uInt16_li version
  field uInt16_li channel
  field uInt32_li rate
  field uInt32_li bytes_per_second
  field uInt16_li bytes_per_sample
  field uInt16_li bits_per_sample
 
type WavChunk
  field (Array Char 4) data
  field uInt32_li length

type WavHeader
  field WavSignature signature
  field WavFormat format
  field WavChunk chunk


function sound_play stream dsp options
  arg_rw Stream stream ; arg_rw Stream dsp ; arg Str options
  var Str ext := options option "extension" Str
  if ext=""
    ext := stream:name (stream:name search_last "." stream:n
  var Pointer:Stream clear
  if ext=".flac" or ext=".ogg"
    stream_pipe (var Str encoded_in) (var Str encoded_out)
    thread
      (var Stream send) open encoded_out out+safe
      while not stream:atend and send=success
        raw_copy stream send 1 2^24
    stream_pipe (var Str wav_in) (var Str wav_out)
    thread
      if ext=".flac"
        execute "flac --decode --stdout --silent -" input en
      eif ext=".ogg"
        execute "oggdec --quiet -o - -" input encoded_in out
    (var Stream receive) open wav_in in+safe
    clear :> receive

#----------------------------------------------------------------------------
#  Pliant Stream emulation


type SoundDriver
  if alsa
    field Address pcm
    field (Array Byte 256) buffer
    field Int garbadge
  else
  else
    clear :> stream
  var Str opt := options ; var Int remain := undefined
  if not (options option "raw")
    clear raw_read addressof:(var WavSignature sign) WavSign
    clear raw_read addressof:(var WavFormat format) WavForma
    clear raw_read addressof:(var WavChunk chunk) WavChunk:s
    opt += " "+(shunt format:channel>1 "stereo" "mono")+" ra
    if (options option "verbose")
      console (cast chunk:length Int)\(cast format:bytes_per
    remain := chunk length
  if not (options option "noopen")
    sound_open dsp opt
  while not clear:atend and dsp=success
    var Int step := raw_copy clear dsp 1 remain
    if remain<>undefined
      remain -= step
  if not (options option "noclose")
    sound_close dsp opt
    field Stream dsp
  field Str title
  field Int channels bits rate unit
  if resample
    field Int record_bits record_rate record_unit
    field CBool record_resample ; field Array:Float record_sum ; field Float record_slice
    field Int record_mini record_maxi
  field DateTime time
  field CBool wav
  field WavHeader wav_header
  field Int wav_offset
  field uInt wav_remain


function sound_play stream options
  arg_rw Stream stream ; arg Str options
  (var Stream dsp) open (options option "device" Str "device
  sound_play stream dsp options
StreamDriver maybe SoundDriver

method drv setup options flags -> status
  arg_rw SoundDriver drv ; arg Str options ; arg Int flags ; arg ExtendedStatus status
  drv title := options option "title" Str
  drv channels := shunt (options option "mono") 1 (options option "stereo") 2 (options option "channels" Int 2)
  drv bits := options option "bits" Int 16
  drv rate := options option "rate" Int 44100
  drv unit := drv:channels*drv:bits\8
  var Int bits := drv bits ; var Int rate := drv rate
  if resample
    drv record_bits := options option "record_bits" Int drv:bits
    drv record_rate := options option "record_rate" Int drv:rate
    drv record_unit := drv:channels*drv:record_bits\8
    drv record_resample := (flags .and. in)<>0 and drv:record_bits>=drv:bits and drv:record_rate>=drv:rate and (drv:record_bits<>drv:bits or drv:record_rate<>drv:rate)
    if drv:record_resample
      drv:record_sum size := drv channels
      for (var Int i) 0 drv:channels-1
        drv:record_sum i := 0
      drv record_slice := 0
      drv record_mini := 0 ; drv record_maxi := 0
      bits := drv record_bits
      rate := drv record_rate
  status := success
  if alsa
    var Int err := snd_pcm_hw_params_malloc (var Address hw)
    if err<>0
      return (failure "snd_pcm_hw_params_malloc "+string:err)
    err := snd_pcm_hw_params_any drv:pcm hw
    if err<>0
      status := failure "snd_pcm_hw_params_any "+string:err
    err := snd_pcm_hw_params_set_access drv:pcm hw SND_PCM_ACCESS_RW_INTERLEAVED
    if err<>0
      status := failure "snd_pcm_hw_params_set_access "+string:err
  if alsa
    var Int err := snd_pcm_hw_params_set_channels drv:pcm hw drv:channels
    if err<>0
      status := failure "snd_pcm_hw_params_set_channels error "+string:err
  else
    if (os_ioctl drv:dsp:stream_handle os_SNDCTL_DSP_CHANNELS (addressof drv:channels))<>0
      status := failure "failed to set audio channels"
    # var Int stereo := channels-1
    # if (os_ioctl drv:dsp:stream_handle os_SNDCTL_DSP_STEREO addressof:stereo)<>0
    #   status := failure "failed to set audio mono/stereo"
  if alsa
    var Int err := snd_pcm_hw_params_set_format drv:pcm hw (shunt bits=8 SND_PCM_FORMAT_S8 bits=16 SND_PCM_FORMAT_S16_LE bits=24 SND_PCM_FORMAT_S24_3LE -1)
    if err<>0
      status := failure "snd_pcm_hw_params_set_format error "+string:err
  else
    if (os_ioctl drv:dsp:stream_handle os_SNDCTL_DSP_SETFMT addressof:bits)<>0
      status := failure "failed to set audio sampling resolution"
  if alsa
    var Int err := snd_pcm_hw_params_set_rate drv:pcm hw rate 0
    if err<>0
      status := failure "snd_pcm_hw_params_set_rate error "+string:err
  else
    if (os_ioctl drv:dsp:stream_handle os_SNDCTL_DSP_SPEED addressof:rate)<>0
      status := failure "failed to set audio sampling rate"
  if alsa
    err := snd_pcm_hw_params drv:pcm hw
    if err<>0
      status := failure "snd_pcm_hw_params "+string:err
    snd_pcm_hw_params_free hw


if resample

  function read_bits a bits channel -> v
    arg Address a ; arg Int bits channel v
    if bits=8
      v := a map Int8 channel
    eif bits=16
      v := a map Int16 channel
    eif bits=24
      memory_copy (a translate Byte 3*channel) addressof:v 3
      if (v .and. 800000h)<>0
        v := v .or. -1000000h
      else
        v := v .and. 0FFFFFFh
  
  
  function write_bits a bits channel v
    arg Address a ; arg Int bits channel v
    if bits=8
      a map Int8 channel := v
    eif bits=16
      a map Int16 channel := v
    eif bits=24
      memory_copy addressof:v (a translate Byte 3*channel) 3
    
method drv read buf mini maxi -> red
  arg_rw SoundDriver drv ; arg Address buf ; arg Int mini maxi red
  if alsa
    var Int frames := maxi\drv:unit
    if resample and drv:record_resample
      var Address record_buf := memory_allocate frames*drv:record_unit null
      var Int err := snd_pcm_readi drv:pcm record_buf frames
      if recover and err<0
        snd_pcm_prepare drv:pcm
        err := snd_pcm_readi drv:pcm record_buf frames
      var Address src := record_buf ; var Float src_period := 1/drv:record_rate
      var Address dest := buf ; var Float dest_period := 1/drv:rate
      for (var Int i) 0 err-1
        var CBool full := drv:record_slice+src_period>=dest_period
        if full
          var Float f := dest_period-drv:record_slice
          for (var Int j) 0 drv:channels-1
            drv:record_sum j += (read_bits src drv:record_bits j)*f
          for (var Int j) 0 drv:channels-1
            var Int u := cast drv:record_sum:j/dest_period/2^(drv:record_bits-drv:bits) Int
            drv record_mini := min drv:record_mini u
            drv record_maxi := max drv:record_maxi u
            u := min (max u -(2^(drv:bits-1)-1)) 2^(drv:bits-1)-1
            write_bits dest drv:bits j u
            drv:record_sum j -= u*2^(drv:record_bits-drv:bits)*dest_period
          dest := dest translate Byte drv:unit
          var Float f :=  drv:record_slice+src_period-dest_period
          for (var Int j) 0 drv:channels-1
            drv:record_sum j += (read_bits src drv:record_bits j)*f
          drv record_slice := f
        else
          for (var Int j) 0 drv:channels-1
            drv:record_sum j += (read_bits src drv:record_bits j)*src_period
          drv record_slice += src_period
        src := src translate Byte drv:record_unit
      red := (cast dest Int) .-. (cast buf Int)
      memory_free record_buf
    else
      var Int err := snd_pcm_readi drv:pcm buf frames
      if recover and err<0
        snd_pcm_prepare drv:pcm
        err := snd_pcm_readi drv:pcm buf frames
      red := shunt err>=0 err*drv:unit 0
  else
    red := drv:dsp:stream_driver read buf mini maxi
  drv:time seconds += red/drv:unit/drv:rate


function sound_record stream options
  arg_rw Stream stream ; arg Str options
  (var Stream dsp) open "device:/dsp" in+safe
  sound_open dsp options
  while not dsp:atend and stream=success
    raw_copy dsp stream 1 2^24
  sound_close dsp options
method drv write buf mini maxi -> written
  oarg_rw SoundDriver drv ; arg Address buf ; arg Int mini maxi written
  written := 0
  if drv:wav and drv:wav_offset<WavHeader:size
    written := min maxi WavHeader:size-drv:wav_offset
    memory_copy buf ((addressof drv:wav_header) translate Byte drv:wav_offset) written
    drv wav_offset += written
    if drv:wav_offset=WavHeader:size
      if (drv setup "channels "+string:(cast drv:wav_header:format:channel Int)+" rate "+string:(cast drv:wav_header:format:rate Int)+" bits "+string:(cast drv:wav_header:format:bits_per_sample Int) out)=failure
        return 0
      drv wav_remain := drv:wav_header:chunk length
  if written<mini
    var Int base := written
    if alsa
      if drv:garbadge<>0
        var Int step := min maxi-written drv:unit-drv:garbadge
        memory_copy (buf translate Byte written) ((addressof drv:buffer) translate Byte drv:garbadge) step
        drv garbadge += step
        written += step
        if drv:garbadge=drv:unit
          var Int err := snd_pcm_writei drv:pcm (addressof drv:buffer) 1
          drv garbadge := 0
      var Int frames := (maxi-written)\drv:unit
      if frames>0
        var Int err := snd_pcm_writei drv:pcm (buf translate Byte written) frames
        if recover and err<0
          snd_pcm_prepare drv:pcm
          err := snd_pcm_writei drv:pcm (buf translate Byte written) frames
        written += shunt err>=0 err*drv:unit 0
      else
        var Int step := min maxi-written drv:unit-drv:garbadge
        memory_copy (buf translate Byte written) ((addressof drv:buffer) translate Byte drv:garbadge) step
        drv garbadge += step
        written += step
    else
      var Int step := drv:dsp:stream_driver write (buf translate Byte written) mini-written maxi-written
      written += step
    drv:time seconds += (written-base)/drv:unit/drv:rate


method drv configure command stream -> status
  arg_rw SoundDriver drv ; arg Str command ; arg_rw Stream stream ; arg ExtendedStatus status
  status := failure


function sound_record file options
  arg Str file options
  (var Stream data) open file out+safe
  sound_record data options
method drv query command stream answer -> status
  oarg_rw SoundDriver drv ; arg Str command ; arg_rw Stream stream ; arg_w Str answer ; arg ExtendedStatus status
  if command="time"
    var DateTime time := drv time
    if alsa
      time seconds += (snd_pcm_status_get_delay drv:pcm)/drv:rate
    else
      if (os_ioctl drv:dsp:stream_handle os_SNDCTL_DSP_GETODELAY addressof:(var Int delay))=0
        time seconds += delay/drv:unit/drv:rate
    answer := string time
    status := success
  eif command="title"
    answer := drv title
    status := success
  eif command="time"
    answer := string drv:time
    status := success
  eif command="channels"
    answer := string drv:channels
    status := success
  eif command="bits"
    answer := string drv:bits
    status := success
  eif command="rate"
    answer := string drv:rate
    status := success
  eif command="unit"
    answer := string drv:unit
    status := success
  eif resample and command="range"
    answer := (string drv:record_mini/(2^(drv:bits-1)-1) "fixed 2")+" "+(string drv:record_maxi/(2^(drv:bits-1)-1) "fixed 2")
    status := shunt drv:record_resample success failure
  else
    status := failure


function sound_play file options
  arg Str file options
  var Str ext := file (file search_last "." file:len) file:l
  if file:len>0 and (file file:len-1)="/"
    (var Stream dsp) open (options option "device" Str "devi
    var Array:FileInfo files := file_list file standard+sort
    var Int first := (options option "track" Int 1)-1
    for (var Int i) first files:size-1
      console "track " i+1 ": "
      (var Stream data) open files:i:name in+safe
      sound_play data dsp options+" verbose"+(shunt i=first 
      if i<>files:size-1      
        var Float silent := options option "silent" Float 0
        for (var Int j) 1 2*(cast silent*44100 Int)
          var Int16 z := 0 ; dsp raw_write addressof:z Int16
method drv close -> status
  arg_rw SoundDriver drv ; arg ExtendedStatus status
  status := success
  if alsa
    snd_pcm_drain drv:pcm
    var Int err := snd_pcm_close drv:pcm
    if err<>0
      status := failure "snd_pcm_close error "+string:err


type SoundFileSystem
  void
FileSystem maybe SoundFileSystem

method fs open name options flags stream support -> status
  arg_rw SoundFileSystem fs ; arg Str name options ; arg Int flags ; arg_rw Stream stream support ; arg ExtendedStatus status
  var Link:SoundDriver drv :> new SoundDriver
  if alsa
    var Str device := options option "device" Str "hw:"+(shunt name<>"" name "0")
    var Int err := snd_pcm_open drv:pcm device (shunt (flags .and. out)<>0 SND_PCM_STREAM_PLAYBACK SND_PCM_STREAM_CAPTURE) 0 # SND_PCM_NONBLOCK
    if err<>0
      return (failure "snd_pcm_open error "+string:err)
  else
  else
    (var Stream data) open file in+safe
    sound_play data options
    status := drv:dsp open (options option "device" Str "device:/dsp"+name) (flags .or. safe)
    if status=failure
      return (failure "failed to open OSS device "+drv:dsp:name)
  if false
    var Str format := options option "sound_format" Str
    if format="flac" or format=".ogg"
      stream_pipe (var Str encoded_in) (var Str encoded_out)
      thread
        (var Stream send) open encoded_out out+safe
        while not stream:atend and send=success and not please_stop
          raw_copy stream send 1 2^24
      stream_pipe (var Str wav_in) (var Str wav_out)
      thread
        if ext=".flac"
          execute "flac --decode --stdout --silent -" input encoded_in output wav_out
        eif ext=".ogg"
          execute "oggdec --quiet -o - -" input encoded_in output wav_out
      (var Stream receive) open wav_in in+safe
      clear :> receive
    else
      clear :> stream
  drv wav := options option "wav"
  drv wav_offset := 0
  if alsa
    drv garbadge := 0
  if (flags .and. out)<>0 or not drv:wav
    status := drv setup options flags
  else
    status := success
  if status=success
    stream stream_driver :> drv
  eif alsa
    snd_pcm_close drv:pcm
  drv time := options option "time" DateTime datetime


gvar SoundFileSystem sound_file_system
pliant_multi_file_system mount "sound:" "" sound_file_system


export sound_play sound_record

#----------------------------------------------------------------------------
#  High level interface


constant soundcache bigcache

gvar Sem play_sem
gvar Stream play_stream
gvar CBool play_stop := false

function sound_play_stop
  play_stop := true
  play_sem request ; play_sem release
  play_stop := false
 
function sound_play_start file options -> status
  arg Str file options ; arg ExtendedStatus status
  var DateTime time := options option "time" DateTime datetime
  var Time skip := options option "skip" Time (time 0 0 0 0)
  time seconds += skip seconds
  sound_play_stop
  play_sem request
  status := play_stream open "sound:" "time "+string:time+" "+options out+safe+soundcache
  if status=success
    thread
      part play "Sound play"
        if file:len>0 and (file file:len-1)="/"
          var Array:FileInfo files := file_list file standard+sorted
          var Int first := (options option "track" Int 1)-1
          for (var Int i) first files:size-1
            console "track " i+1 ": "
            (var Stream data) open files:i:name in+safe+soundcache
            while not play_stop and (raw_copy data play_stream 1 2^22)>0
              void
        else
          (var Stream data) open file in+safe+soundcache
          if skip:seconds>0
            (play_stream query "rate") parse (var Int rate)
            (play_stream query "unit") parse (var Int unit)
            var Int i := cast skip:seconds Int
            if i>skip:seconds
              i -= 1
            var Float r := skip:seconds-i
            data configure "seek "+(string 1n*i*rate*unit+(cast r*rate Int)*unit)
          while not play_stop and pliant_execution_phase=execution_phase_run and (raw_copy data play_stream 1 2^22)>0
            void
        play_stream close
        play_sem release
  else
    play_sem release

function sound_play_title -> title
  arg Str title
  title := play_stream safe_query "title"

function sound_play_time -> dt
  arg DateTime dt
  if not ((play_stream safe_query "time") parse dt)
    dt := undefined


gvar Sem record_sem
gvar Stream record_stream
gvar CBool record_stop := false

function sound_record_stop
  record_stop := true
  record_sem request ; record_sem release
  record_stop := false
 
function sound_record_start file options -> status
  arg Str file options ; arg ExtendedStatus status
  sound_record_stop
  record_sem request
  status := record_stream open "sound:" options in+safe+soundcache
  if status=success
    thread
      part record "Sound recording"
        (var Stream data) open file out+safe+soundcache
        if recover2
          part record2
            while not record_stop and pliant_execution_phase=execution_phase_run and (raw_copy record_stream data 1 2^22)>0
              void
            if not record_stop and record_stream=failure and (record_stream open "sound:" options in+safe+soundcache)=success
              restart record2    
        else
          while not record_stop and pliant_execution_phase=execution_phase_run and (raw_copy record_stream data 1 2^22)>0
            void
        record_stream close
        record_sem release
  else
    record_sem release

function sound_record_title -> title
  arg Str title
  title := record_stream safe_query "title"

function sound_record_time -> dt
  arg DateTime dt
  if not ((record_stream safe_query "time") parse dt)
    dt := undefined

function sound_record_range -> r
  arg Str r
  r := record_stream safe_query "range"

  
export sound_play_start sound_play_title sound_play_time sound_play_stop
export sound_record_start sound_record_title sound_record_time sound_record_range sound_record_stop