Страница Ивана Рощина > Статьи >

© Иван Рощин, Москва

ZXNet: 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru

Изменение порядка каналов в модулях Impulse Tracker

Радиомир. Ваш компьютер» 7/2005)

Введение
Текст программы
Руководство пользователя
Источники

Введение

Иногда бывает нужно изменить порядок каналов в музыкальном модуле, написанном в Impulse Tracker, — например, чтобы каналы с одним инструментом располагались рядом для облегчения последующего редактирования. Но в самом Impulse Tracker нет удобного способа это сделать. Можно, конечно, добиться нужного результата с помощью копирования блоков, но это большой объём ручной работы.

Я пробовал найти в Интернете готовую программу для изменения порядка каналов в it-файлах или хотя бы упоминание о ней. Увы, безуспешно — хотя, например, для xm-файлов (Fast Tracker) такая программа была обнаружена. Ничего не оставалось, как написать программу самому. :) До этого мне уже приходилось изучать формат модулей IT, когда я писал конвертор Pro Tracker 3 –> Impulse Tracker, так что задача не показалась сложной.

Текст программы

/* =============================
     File: "reord_it.c"
     Compiler: Turbo C 2.0
 ============================= */

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#define byte unsigned char    /* 1 байт.  */
#define word unsigned int     /* 2 байта. */

/* ==================== Описание функций ==================== */

int get_new_order (char* s);
int read_2 (char** s, int* n1, int* n2);
FILE* fopen_1 (char* name, char* mode);
void fclose_1 (FILE* f);
int fgetc_1 (FILE* f);
void fputc_1 (int c, FILE* f);
void fseek_1 (FILE* f, long offset, int whence);

/* ======================= Переменные ======================= */

int new_order[64];      /* Для каждого канала здесь хранится
                           его новый номер. */
byte pan_vol_old[128];  /* Массивы со старыми и новыми   */
byte pan_vol_new[128];  /* значениями начальной панорамы */
                        /* и громкости каналов.          */

/* ================== Пошла сама программа ================== */

int main (int argc, char *argv[])
{
 FILE *f_src;
 int i,j;

 printf ("\n\
+----------------------------------------------------------+\n\
|Program for reordering channels in Impulse Tracker modules|\n\
|(c) Ivan Roshin, Moscow, 18 Jul 2004                      |\n\
|E-mail: bestview@mtu-net.ru      WWW: http://www.ivr.da.ru|\n\
+----------------------------------------------------------+\n\
");

 if (argc!=3)
 {
  printf ("\n\
Syntax: reord_it.exe NewChannel=OldChannel,... it-file\n\n\
Example: for swap channels 2 and 5, and swap channels 1 and 3\n\
         in it-file 'demo.it', type:\n\n\
reord_it.exe 2=5,5=2,1=3,3=1 demo.it\n\n");
  exit(1);
 }

 /* Обрабатываем указанный в командной строке аргумент,
    определяющий новый порядок расположения каналов. */

 if (get_new_order(argv[1]))
 {printf ("\nIncorrect argument!\n\n"); exit (1);}

 /* Открываем исходный файл. */

 f_src=fopen_1(argv[2],"rb+");
 printf ("File: %s  ",argv[2]);

/* =================== Создание bak-файла =================== */

 {
  FILE *f_bak;
  char name_bak[13];

  /* Формируем имя bak-файла из имени исходного файла. */

  i=0;
  while (!((argv[2][i]=='.')||(argv[2][i]==0)))
  {
   name_bak[i]=argv[2][i];
   i++;
   if (i>8) {printf ("\nIncorrect file name!\n\n"); exit (1);}
  }
  strcpy(name_bak+i,".bak");

  /* Копируем содержимое исходного файла в bak-файл. */

  f_bak=fopen_1(name_bak,"wb");
  while ((i=fgetc_1(f_src))!=EOF) fputc_1 (i,f_bak);
  fclose_1(f_bak);
 }

/* =================== Обработка it-файла =================== */

 /* Проверка сигнатуры: первые 4 байта - "IMPM". */

 {
  byte signature[4];

  fseek_1 (f_src,0L,SEEK_SET);
  if (fread(&signature,(size_t)4,1,f_src)!=1)
  {printf ("\nRead error!\n\n"); exit (1);}
  if (strncmp(signature,"IMPM",(size_t)4)!=0)
  {printf ("\nIncorrect file format!\n\n"); exit (1);}
 }

 /* Читаем начальные значения панорамы и громкости каналов,
    меняем их порядок в соответствии с массивом new_order
    и записываем обратно. */

 fseek_1 (f_src,0x40L,SEEK_SET);
 if (fread(&pan_vol_old,(size_t)128,1,f_src)!=1)
 {printf ("\nRead error!\n\n"); exit (1);}

 for (i=0;i<64;i++)
 {
  pan_vol_new[new_order[i]-1]=pan_vol_old[i];      /* Panning */
  pan_vol_new[new_order[i]-1+64]=pan_vol_old[i+64]; /* Volume */
 }

 fseek_1(f_src,0x40L,SEEK_SET);
 if (fwrite(&pan_vol_new,(size_t)128,1,f_src)!=1)
 {printf ("\nWrite error!\n\n"); exit (1);}

 /* Обработка паттернов. */

 {
  word OrdNum;    /* Кол-во позиций. */
  word InsNum;    /* Кол-во инструментов. */
  word SmpNum;    /* Кол-во сэмплов. */
  word PatNum;    /* Кол-во паттернов. */
  long current_p; /* Смещение до текущего паттерна. */
  word rows;      /* Кол-во строк в текущем паттерне. */

  fseek_1(f_src,0x20L,SEEK_SET);
  OrdNum=fgetc_1(f_src); OrdNum|=fgetc_1(f_src)<<8;
  InsNum=fgetc_1(f_src); InsNum|=fgetc_1(f_src)<<8;
  SmpNum=fgetc_1(f_src); SmpNum|=fgetc_1(f_src)<<8;
  PatNum=fgetc_1(f_src); PatNum|=fgetc_1(f_src)<<8;

  for (i=0;i<PatNum;i++)
  {
   fseek_1(f_src,0xC0L+OrdNum+InsNum*4L+SmpNum*4L+i*4L,SEEK_SET);

   /* Читаем смещение до текущего паттерна. */

   current_p=fgetc_1(f_src);
   current_p|=(long)fgetc_1(f_src)<<8;
   current_p|=(long)fgetc_1(f_src)<<16;
   current_p|=(long)fgetc_1(f_src)<<24;

   /* Если смещение равно нулю - значит, пустой паттерн. */

   if (current_p==0) continue;

   /* Позиционируемся на начало паттерна + 2 байта, читаем
      кол-во строк в паттерне и пропускаем 4 байта. */

   fseek_1(f_src,current_p+2,SEEK_SET);
   rows=fgetc_1(f_src); rows|=fgetc_1(f_src)<<8;
   fseek_1(f_src,4L,SEEK_CUR);

   /* Обработка строк паттерна. */

   for (j=0;j<rows;j++)
   {
    byte old_mask_var[64];
    byte ch_var;
    byte channel;
    byte mask_var;

    /* Обработка одной строки. */

    while ((ch_var=fgetc_1(f_src))!=0)
    {
     /* В младших битах прочитанного байта - номер канала.
        Изменяем его по таблице new_order и записываем обратно,
        сохраняя при этом значение старшего бита. */

     channel=(ch_var-1)&63;
     ch_var=new_order[channel]|(ch_var&128);
     fseek_1(f_src,-1L,SEEK_CUR);
     fputc_1 (ch_var,f_src);
     fseek_1(f_src,0L,SEEK_CUR); /* Почему-то без этого
                                    не работает. :( */
     if (ch_var&128)
     {
      mask_var=fgetc_1(f_src);
      old_mask_var[channel]=mask_var;
     }
     else mask_var=old_mask_var[channel];

     /* Пропускаем неизменяемые байты строки паттерна. */

     if (mask_var&1) fgetc_1 (f_src);
     if (mask_var&2) fgetc_1 (f_src);
     if (mask_var&4) fgetc_1 (f_src);
     if (mask_var&8) {fgetc_1 (f_src); fgetc_1 (f_src);}
    }
   }
  }
 }
 fclose_1 (f_src);
 printf ("OK!\n\n");
 exit (0);
}

/* =============================================================
   Функция get_new_order - обработка указанного в командной
   строке аргумента, определяющего новый порядок расположения
   каналов (т.е. анализ строки вида "7=2,2=15,14=3,...",
   построение на основе этих данных массива new_order, где для
   каждого канала хранится его новый номер, и проверка
   корректности полученного массива).
   Вход: s - указатель на обрабатываемый аргумент.
   Выход: 0 - OK, 1 - ошибка.
 ============================================================ */

int get_new_order (char* s)
{
 int n1,n2;
 int i,j;

 for (i=0;i<64;i++) new_order[i]=i+1;
 while (*s!=0) /* Пока строка не кончилась. */
 {
  if (read_2(&s,&n1,&n2)) return 1;
  new_order[n2-1]=n1;
 }

 /* Проверка корректности полученного массива new_order (то есть
    проверка, не допустил ли пользователь ошибок при указании
    нового порядка расположения каналов): в массиве должно
    присутствовать каждое из чисел от 1 до 64 (то есть массив
    должен состоять из всех чисел от 1 до 64 в любом порядке).
    В самом деле, если в массиве нет какого-то из этих чисел,
    то соответствующему каналу формируемого модуля не будет
    соответствовать никакой канал исходного модуля, а это
    недопустимо, ведь можно изменять только порядок каналов. */

 for (i=1;i<=64;i++)
 {
  for (j=0;j<64;j++) {if (new_order[j]==i) goto m1;}
  return 1;
  m1:;
 }
 return 0;
}

/* =============================================================
   Функция read_2 - чтение первой пары значений из строки вида
   "7=2,2=15,14=3,...".
   Вход: s - адрес указателя на строку (после успешного выхода
         из функции этот указатель изменяется так, чтобы
         указывать на следующую пару значений в строке - т.е.
         на "2=15,14=3,..."); n1 и n2 - адреса, по которым надо
         поместить первое и второе прочитанные значения (в
         формате int).
   Выход: 0 - OK, 1 - ошибка.
 ============================================================ */

int read_2 (char** s, int* n1, int* n2)
{
 if (isdigit(**s)) {*n1=**s-'0'; (*s)++;} else return 1;
 if (isdigit(**s)) {*n1=*n1*10+**s-'0'; (*s)++;}
 if (**s!='=') return 1;
 (*s)++;
 if (isdigit(**s)) {*n2=**s-'0'; (*s)++;} else return 1;
 if (isdigit(**s)) {*n2=*n2*10+**s-'0'; (*s)++;}
 if (**s==',') {(*s)++; return 0;}
 if (**s==0) return 0; else return 1;
}

/* =============================================================
   Аналоги некоторых функций для работы с файлами (fopen,
   fclose, fgetc, fputc, fseek). При ошибке прерывают выполнение
   программы, печатая соответствующее сообщение; таким образом,
   после вызова этих функций не надо проверять, была ли ошибка.
 ============================================================ */

FILE* fopen_1 (char* name, char* mode)
{
 FILE* f;
 char* s;
 char* s1="read";
 char* s2="write";

 f=fopen(name,mode);
 if (f==NULL)
 {
  if (mode[0]=='r') s=s1; else s=s2;
  printf ("\nCan't open file \"%s\" for %s!\n\n",name,s);
  exit (1);
 }
 return (f);
}

void fclose_1 (FILE* f)
{
 if (fclose(f)==EOF) {printf ("\nClose error!\n\n"); exit (1);}
}

int fgetc_1 (FILE* f)
{
 int c;
 c=fgetc(f);
 if ferror(f) {printf ("\nRead error!\n\n"); exit (1);}
 return (c);
}

void fputc_1 (int c, FILE* f)
{
 if (fputc(c,f)==EOF) {printf ("\nWrite error!\n\n"); exit (1);}
}

void fseek_1 (FILE* f, long offset, int whence)
{
 if (fseek(f,offset,whence)!=0)
 {printf ("\nPosition error!\n\n"); exit (1);}
}
Скачать исполняемый файл программы (для DOS) (7 КБ ZIP)
Скачать листинг программы в текстовом виде (3 КБ ZIP)

Руководство пользователя

Формат командной строки:

reord_it.exe информация_об_изменении_порядка_каналов it-file

Если запустить программу без аргументов, она выдаст краткую справку о работе с ней.

Информация об изменении порядка каналов представляет собой разделённые запятой пары чисел вида NewChannel=OldChannel, где NewChannel — номер канала, куда будет перенесена информация, а OldChannel — номер канала, откуда будет перенесена эта информация. То есть указывается соответствие каналов в исходном и получаемом модулях. Например, надо поменять местами содержимое 1 и 2 каналов, тогда в командной строке указываем «1=2,2=1» (без кавычек, без пробела после запятой): в первый канал переносится информация из второго, а во второй — из первого. Можно записать и наоборот: «2=1,1=2» — результат будет тем же самым.

Ещё пример: надо, чтобы в первом канале оказалось то, что раньше было во втором, во втором — то, что раньше было в третьем, а в третьем — то, что раньше было в первом. В командной строке указываем «1=2,2=3,3=1».

Обратите внимание: когда информация перемещается в некоторый канал X из канала Y, то информация, которая была раньше в канале X, также должна быть перемещена в какой-либо канал: программа только меняет порядок каналов, ни из какого канала информация не должна пропасть! Поэтому неправильно будет указать в командной строке, скажем, «1=2,2=3», так как тогда неясно, куда перейдёт информация из канала 1. Программа следит за этим и в подобной ситуации выдаст сообщение «Incorrect argument!».

Обрабатываемый it-файл должен находиться в текущем каталоге. Перед началом его обработки программа создаёт его резервную копию — с тем же именем и расширением «bak». Это необходимо, чтобы исключить возможность потери информации, содержавшейся в исходном файле, если он не является корректным it-модулем или если работа программы по какой-либо причине не будет доведена до конца.

Программа проверяет, чтобы обрабатываемый файл начинался с символов «IMPM»; если это не так, значит, это не it-модуль, и вы увидите сообщение «Incorrect file format!».

Программа возвращает операционной системе код завершения: 0 — успешное завершение, 1 — ошибка. Сообщение об ошибке также выводится на экран.

При изменении порядка каналов меняются не только данные в паттернах, но и начальные значения панорамы и громкости каналов.

Источники

  1. Описание формата модулей Impulse Tracker (файл «ITTECH.TXT» из комплекта поставки Impulse Tracker 2.14).

Другие мои статьи о создании и/или обработке модулей Impulse Tracker:

1. 

«Конвертор Pro Tracker 3 –> Impulse Tracker». «Радиомир. Ваш компьютер» 12/2004.

Страница Ивана Рощина > Статьи >