/* vidc.c */
/* 8-bit raw VIDC sound handling */

#include <stdio.h>
#include <stdlib.h>
#include "swis.h"
#include "vidclin.h"
#include "vidclog.h"
#include "errors.h"
#include "log.h"
#include "vidc.h"

#ifndef OS_SynchroniseCodeAreas
#define OS_SynchroniseCodeAreas 0x6e
#endif

/* The ints are mainly ARM instructions,
   some branches (terminated by _) need altering at installation */
typedef struct {
  int fill_, update_, gate_on, gate_off_, instantiate, free, rtn, name_offset;
} raw_voice_header;

typedef struct extended_voice_header {
  raw_voice_header raw;
  int gate_on;
  int branch_to_gate_on_;
  int instantiate_stack, instantiate_addr;
  int branch_to_instantiate_;
  int free_stack, free_rtn;
} extended_voice_header;

struct vidc_descriptor {
  int th_slot;
  int sys_slot;
  const char *data_start;
  const char *data_end;
  char *log_table;
  char *lin_to_log;
  extended_voice_header voice_header;
  int poll_word;
  char voice_name[12];
};

struct dttsamp_header {
  int special_word;
  unsigned char note;		/* 13 = C2 */
  unsigned char volume;
  char reserved1, reserved2;
  int period;			/* 1.6384e10 / frequency */
  int rpt_start, rpt_end, sus_start, sus_end;
  int length;
  char name[32];
};

#define MAX_VOICES 32

static vidc_handle voice_slots[MAX_VOICES];

_kernel_oserror *
vidc_initialise(void)
{
  int i;
  for (i = 0; i < MAX_VOICES; ++i)
    voice_slots[i] = 0;
  return 0;
}

_kernel_oserror *
vidc_finalise(void)
{
  _kernel_oserror *result = 0;
  int slot;
  for (slot = 0; slot < MAX_VOICES; ++slot)
  {
    if (voice_slots[slot])
    {
      _kernel_oserror *e = vidc_remove_sample(voice_slots[slot]);
      if (e && !result)
        result = e;
    }
  }
  return result;
}

#define FIXBRANCH(br, of) \
  ((br) = ((((br) & 0xffffff) + (of)) & 0xffffff) | ((br) & 0xff000000))

/* SWIs */
_kernel_oserror *
vidc_install_sample(const char *data, size_t size, int sys_slot, int linear,
                    vidc_handle *handle_out, int *slot_out)
{
  _kernel_oserror *e;
  vidc_handle handle;
  int slot;
  int branch_correction;
  extended_voice_header *voice_header;
  voice_header = linear ? vidclin_extended_header_template
                        : vidclog_extended_header_template;
  for (slot = 0; slot < MAX_VOICES && voice_slots[slot]; ++slot);
  if (slot >= MAX_VOICES)
    return &error_too_many_voices;
  handle = malloc(sizeof(struct vidc_descriptor));
  if (!handle)
    return &error_memory;
  LOG(("Allocated %d bytes at %p OK for vidc_descriptor\n",
       sizeof(struct vidc_descriptor), handle));
  handle->th_slot = slot;
  handle->data_start = data;
  handle->data_end = data + size;
  handle->poll_word = 0;
  sprintf(handle->voice_name, "THSVoice%02x", slot);
  /* Make copy of template voice header and amend branches */
  handle->voice_header = *voice_header;
  branch_correction = (((int) voice_header
                        - ((int) handle
                           + offsetof(struct vidc_descriptor, voice_header)))
                       >> 2) & 0x00ffffff;
  FIXBRANCH(handle->voice_header.raw.fill_, branch_correction);
  FIXBRANCH(handle->voice_header.raw.update_, branch_correction);
  FIXBRANCH(handle->voice_header.raw.gate_off_, branch_correction);
  FIXBRANCH(handle->voice_header.branch_to_gate_on_, branch_correction);
  FIXBRANCH(handle->voice_header.branch_to_instantiate_, branch_correction);
  /* Now need to sync StrongARM caches for above changes */
  _swix(OS_SynchroniseCodeAreas, _INR(0,2), 1,
        &handle->voice_header, &handle->voice_header.branch_to_instantiate_);
  /* Install voice */
  e = _swix(Sound_InstallVoice, _INR(0,1)|_OUT(1),
            &handle->voice_header, sys_slot, &handle->sys_slot);
  if (!e && !handle->sys_slot)
    e = &error_install_failure;
  if (e)
  {
    free(handle);
    return e;
  }
  LOG(("Installed voice OK\n"));
  /* Finally make this a listed sample */
  voice_slots[slot] = handle;
  *handle_out = handle;
  *slot_out = handle->sys_slot;
  return 0;
}

_kernel_oserror *
vidc_remove_sample(vidc_handle handle)
{
  _kernel_oserror *e;
  int removed = 0;
  /* Remove voice from system */
  LOG(("Removing voice %d %s from system slot %d\n",
       handle->th_slot, handle->voice_name, handle->sys_slot));
  _swix(Sound_RemoveVoice, _IN(1)|_OUTR(0,1), handle->sys_slot, &e, &removed);
  if (removed)
  {
    LOG(("That seemed to go OK\n"));
    e = 0;
  }
  /* Remove sample from our list */
  voice_slots[handle->th_slot] = 0;
  free(handle);
  return e;
}

_kernel_oserror *
vidc_get_voice_slot(vidc_handle handle, int *slot_out)
{
  *slot_out = handle->sys_slot;
  return 0;
}

_kernel_oserror *
vidc_channel_in_use(int channel, int *is_in_use)
{
  _kernel_oserror *e;
  int current, end, status;
  /* Read whether current position in SCCB is still within sample data */
  e = _swix(Sound_ReadControlBlock, _INR(0,1)|_OUT(0)|_OUT(2),
            channel, 8, &status, &current);
  if (e)
    return e;
  if (!status)
    return &error_reading_sccb;
  e = _swix(Sound_ReadControlBlock, _INR(0,1)|_OUT(0)|_OUT(2),
            channel, 12, &status, &end);
  if (e)
    return e;
  if (!status)
    return &error_reading_sccb;
  *is_in_use = current >= end ? 0 : 1;
  return 0;
}

_kernel_oserror *
vidc_get_poll_word(vidc_handle handle, int **poll_word)
{
  *poll_word = &handle->poll_word;
  return 0;
}
