2006年02月21日

真的是很吃惊,在继互联网最大规模的,域名备案活动之后,又将激起一场新的暗流!

因为电子邮件服务器备案对于大多数的网民没有影响,但若干年后,我们将会吃尽这里面的苦头!

1、失去了通信的自由

所有的邮件都需要被至少插上一个标签:正常邮件 OR 广告邮件

广告邮件,又需要被明确区分为:受用户许可的广告邮件 OR 未受许可以广告邮件。

除此以外,还有可能出现第三种类别:未知的广告邮件。

所有的邮件都需要被脱光衣服还有裤子,标上草签,一个个赤裸裸地通过SMTP服务器。很明显如果你的邮件是包含了无法解包的信息,那么就完全有理由说,你是一个未知的广告邮件。出于管理的需要,邮件就消失了。

2、失去了通信的安全

发一封信给我的朋友,但这封信是不是能够到达?也许我只是建议他到学校下载一份最新的文件,却有可能被禴为是一封未受明确许可以广告邮件。因为没有机器能够理解,一个WORD文件中的真正内涵。

也许一个非常个人的邮件,却需要在七个人的小房间里,用人工来表决,因为机器永远都不能理解诗篇。任何一封邮件,都不能确保能够安全地到达目的地。

3、失去了….不能说了

几个预言:

1、邮件服务器备案,再掀高潮

2、很多的外贸公司,将不得不使用海外的服务器来发送他们的商业邮件。

3、若干个大邮件服务商的用户满意度将再次下降。

4、VOA 又可以多开几期访谈了。

2004年11月25日

从网上找的,已经将用BC写的改成了VC的,由于对音乐的理解比乐盲还差,对于程序中转换是否有问题我也不得而知,反正用VC生成的MIDI文件听起来惨不忍睹。



 对于制作MIDI音乐来说,比播放MIDI文件本身更复杂得多。我们得了解一些乐理常识和MIDI文件结构。


一、MIDI文件结构分析   MIDI文件包含首部块(Header Chunk)和音轨块(Track Chunk)两部分。其格式一般如下:


  MThd <数据长度> <Header数据>    //首部块
  …….
  Mtrk <数据长度> <Track数据>     //音轨块
  Header Chunk 结构为:
  char MidiId[4];
  long length;
  int  foarmt;
  int  TrackNum;
  int  division;


其中:


  MidiId称为MIDI文件头标志,一般将其设置为MThd;


  length为文件首部数据长度(除它本身和文件头标志占用的字节以外),通常它设置为6,即format,TrackNum和division共占用的字节数据长度;


  format表示MIDI文件存放的格式,当前只有3种格式:


  0 表示MIDI文件只有一个Track Chunk;
  1 表示MIDI文件只有一个或多个Track Chunk;
  2 表示MIDI文件只有一个或多个各处独立的Track Chunk。
  division指定计数的方法,一种随时间计数(最高位设置为0时),另一种使用制式的时间码(最高位设置为1时)。这里,主要介绍随时间计数的一种格式。其各位意义如下:


  ┌─┬─────────┐
  │0 │ 每一拍的计数值   │
  └─┴─────────┘
  b15     b14  ̄ b0


  其最高位一定要设置为0,其它的15位表示每一拍的计数值。如该数据为96(以八分音符为一拍),则表示一个四分音符延时数应该为192。


  另外,在MIDI文件中,long和int型数据均将高字节值存放入低地址上,如一个long型数据为0×45678,则在文件中,存放的结果为:0×00,0×04,0×56,0×78。而在内存中,int,long的变量值通常将崐高字节值存放高地址上。因此,存放数据时,应该作一下调整。


  Track Chunk为用来播放歌曲的数据信息。每一个Track Chunk是一组简单的MIDI码(包括一些非MIDI码)的集合。它又由头部信息和崐若干个Mtrk event组合而成。


  头部结构和意义为:


  char TrackChunkId[4];      //Track Chunk标志MTrk
  long TrackChunkMsgLength;  //该Track Chunk信息长度


  而Mtrk event是由时间计数值(dela-time)和event(MIDI码信崐息)组合成的。即:


  <Mtrk event>=<dela-time> <event>


  <dela-time>使用可变长度的形式存储数据,它代表处理event之前要计数时间值。 它在音乐中,即表示拍数。通常音乐开始演奏时,总是将计数时间值设置为0。为了能连续处理两个event,我们可以将deta-time设置为0。如:3和5同时演奏2拍(每一拍计数值为24),可以设置如下:


  deta-time    event
  0         开始演奏3
  0         开始演奏5
  48        停止3演奏
  0         停止5演奏


  event表示MIDI码信息集,如0×9n表示开始发音,0×8n表示关闭发音等等(下有说明)。


  上述的dela-time使用可变长度的形式表示数据值。可变长度形崐式是MIDI文件中对于大于8位的数据打用的一种存储方式,它把每一个数据定义为7位,剩下的最高位作为数据长度的识别。当这一位为0时,表示数据是最后一个,若为1,则表示还有下一个。


  如:数值0×3fff,可变长度形式便为0xff,0×7f;0×4000则应该为0×81,0×80,0×00。此数据的转换可以参阅WriteLenghtToBuf()函数。


二、常见MIDI码说明


  MIDI码是制定音乐交换的信息码,它使用串行非同步传送,因此数据码是用多码形式。第一个MIDI码是状态码,剩余的都是数据码,其长度视状态而定。


  以下是一些常见的MIDI码。


  1、开始发音(0×9n)


  格式为:0×9n note speed


  它一共占用3个字节,n表示通道号,取值0-15。MIDI可以同时演奏16个通道,用此指定在哪一个通道上发音(以下n相同)。


  note表示音高数值,即音阶码值。如C4(中音1)为60,它的取值在0xc和0×6c之间(具体码值,可参考「参考书籍1」)。


  speed表示按键时的速度,用此表示音的力度。若没有力度感,可以将其设置为64,若为0,表示关闭发音。


  如:在第2通道上开始演奏3,则MIDI码便为0×91,63,40。


  MIDI规范还规定,若连续向同一通道上发送多个音,则可以不指出状态码。如上述同时演奏3,5,MIDI码便为:0×91,63,40,65,40。


  2、关闭发音(0×8n)。


  格式:0×8n note speed


  说明同上。通常它用0×9n,note,0来代替。


  3、切换音色(0xcn)。


  格式:0xcn,program


  program表示音色代码,0 ̄255之间,如Acou Piano 1(电钢1值为0),Synth Bass 1(电贝司1值为64)等(详见「参考书籍1」)。


  4、设置音量大小


  格式:0xbn ,07,size


  0xbn,39,size


  7,表示设置主音量的高字节值;39表示设置主音量的低字节值。


  5、设置时间记号


  格式:0xff 0×58 04 nn dd cc bb


  nn和dd直接对应到谱号的数字,dd使用2的指数。如3/8,则nn=3,dd=3。cc是代表第次节拍器打后的时间是几个MIDI clock。bb通常设置为8表示多少个MIDI clock等于1/4 拍。


  6、设置演奏速度


  格式:0xff 0×51 03 tt tt tt


  tt tt tt 表示第一拍定义多少个Miscro Seconds。它即是用来崐变演奏的速度。


  7、写歌词


  格式:0xff 0×05 len text


  len表示歌词的长度,text表示歌词文本码。


  8、磁道结束


  格式:0xff,0×2f,00


  它表示结束点。每外track chunk后都应该有此MIDI码。


三、MIDI信息文本文件制作


  为了能制成符合规范的MIDI文件,我们在此规定MIDI信息文本制作格式如下:


  [MIDI]
  <调号>,<节拍>,<每分钟节拍数>,<音轨个数>
  [1]
  …..
  [n]
  ….


  说明:


  1、调号,占用一个字符,必须为A、B、C、D、E、F、G,否则视为C调;


  2、节拍,取值如下:2/4,3/4,4/4,3/8,6/8….等。


  3、每分钟节拍数:表示每分钟演奏的节拍总数,取值在40-200崐之间,否则视为120。


  4、音轨个数表示此歌曲声部数。如三声部,可将其设置为3。


  5、[n]后表示此音轨的音乐信息。有如下说明字符组合而成。


音高:


  高音 C  D   E   F   G   A   B
  中音 1  2   3   4   5   6   7
  低音 c  d   e   f   g   a   b



  若某音升半音,则在其后加#号;降半音,在其后加b字符。


  音长: -(延长四分音符的一拍)、_(8分音符,后可带符点)、=(16分音符,后可带符点)、.(附点音符,后不可带符点)、:(32分音符,后可带符点)、;(64分音符,后不可带符点)。


  说明:在书写时,请先写完整的音高,再写音长,如简谱中的”3-”,则应该为”3#-”。


  Pn:表示设置音色,取值1-256之间。
  {}:歌词或注释。
  |: 表示小节分隔符。
  \: 后继音均降八度
  /: 后继音均升八度
  Sn:音量大小,n数值越大,音量越大。
  其它的字符,视为非法字符。


  以下为歌曲《解放军的天》片断MIDI文本文件。


  [MIDI]



  F,2/4,150,2



  [1]



  P53



  /3=3=3=2= 3_.2=  | 1_1=e= g | 3=3=3=2= 3_3=2= | 1_1=e=  g |



  \6_. 5=  6_.5=  | 6_C_ 3_5_| 6=6=6=6= 6_6_   | 5=6=C_ C_3_|



  2_.3=   5_C_    | 6=5=3_ 5   | 6_. / 1= 2_.1=| 2_0_ 3_.2= |



  1_0_ 2_2_    |  \5_.6=  /1_3_ | 3=1=a_ 1 \ |



  [2]



  P53  \



  1_C_ 5_G_ |  1_c_ 5_G_ | 1_C_ 5_G_ |  1_c_ 5_G_ |



  a_6_ 4_6_ | 1_5_ 3_ 5_ | a_6_ 4_6_ | 1_5_ 3_ 5_ |



  1_3_ 5_1_ | 1_6_ 4_6_ | 2_5_ 1_5_ | g_5_ 1_5_ |



  1_5_ 3_5_  | 1_6_ 4_6_| 3_2_ 1    |



四、程序实现  



  以下为MIDI文件生成的全部源程序,经Borland c++3.1编译、连接通过。


  #include <stdlib.h>
  #include <stdio.h>
  #include <io.h>
  #include <string.h>
  #define C1 60 //C调1的键名值
  #define FOURPAINUM 64 //1/4音符计数
  #define MIDICLOCK 24 //每1/64音符的MIDICLOCK数
  #define JumpNullChar(x) \ //跳过空字符
   { \
   while(*x==’ ‘ \
   ||*x==’\t’ \
   ||*x==’\n’ \
   ||*x==’|') \
   x++; \
   };
  enum ERRORCODE{ //处理错误信息
   ChangeOK, //转换成功
   TextFileNotOpen, //文本文件不能打开
   MidiFileCanNotCreate, //指定的MIDI文件不能建立
   TextFileToBig, //文本文件太大
   MallocError, //内存分配错误
   InvalideChar, //在文本文件中出现了非法字符
   NotFoundTrack, //没有找到指定的磁道信息
   NotMIDITextFile, //文本文件不是MIDI文本文件
   };
  void SWAP(char *x,char *y) //两数据交换
  { char i;
   i=*x;
   *x=*y;
   *y=i;
  }
  union LENGHT
  { long length;
  char b[4];
  } ;
  struct MH { //MIDI文件头
  char MidiId[4]; //MIDI文件标志MThd
  long length; //头部信息长度
  int format; //存放的格式
  int ntracks; //磁道数目
  int PerPaiNum; //每节计算器值
  };
  struct TH //音轨头
  { char TrackId[4]; //磁道标志MTrk
   long length; //信息长度
  } ;
  class MIDI
  {
  public:
  char ErrorMsg[100]; //错误信息
  private:
  unsigned char *TextFileBuf,
   *TextFileOldBuf;
  unsigned char *MidiFileBuf,
   *MidiFileOldBuf;
  char OneVal; //某调时,1的健值
  char PaiNum; //第一小节节拍总数
  char OnePaiToneNum; //用几分音符作为一基本拍
  public:
  //将符全MIDI书定格式的文本文件生成MIDI文件
  int ChangeTextToMidi(char *TextFileName,
   char *MidiFileName);
  char *GetErrorMsg() //获取错误信息
   { return(ErrorMsg);}
  private:
  char GetCurPaiSpeed(int n); //取当前拍的按下强度
  void WriteSoundSize(char ntrack,unsigned int );
  void SetOnePaiToneNum(int n)
   { OnePaiToneNum=n; };
  void SetOneVal(char *m) ; //取m大调或小调时,1的实际键值
  char GetToneNum(char c, //取记名对应的键值
   char flag) ;
  void WriteMHToFile(long length, //建立MIDI文件头
   int format,
   int ntracks,
   int PerPaiNum,
   FILE *fp);
  void WriteTHToFile(long lenght,
   FILE *fp); //建立MIDI磁道头
  void WriteTrackMsgToFile(FILE *fp);
  //将磁道音乐信息定入文件中
  void WriteSpeed(int speed);
  void SetPaiNum(int n)
   { PaiNum=n;}
  long NewLong(long n); //新的long值
  int NewInt(int n) //新的int值
   { return(n<<8|n>>8);}
  //将n改为可变长度,内入buf处
  void WriteLenghtToBuf(unsigned long n,
   char *buf);
  void ChangePrommgram(char channel, //设置音色
   char promgram);
  void NoteOn (char n, //演奏乐音
   char speed,
   unsigned long delaytime);
  void WriteNoteOn(char,char,char ,unsigned long) ;
  void WriteTextMsg(char *msg); //定一串文本信息
  void WriteTimeSignature(char n, //设置时间信息
   char d);
  void WriteTrackEndMsg(); //设置磁道结束信息
  };


  /**************************************************
  /* 作用:将符合MIDI文本文件的text文件转换成MIDI */
  /* 文件. */
  /* 入口参数:TextFileName 文本文件名 */
  /* MidiFileName MIDI文件名 */
  /* 出口参数:见 ERRORCODE 说明 */
  /*************************************************/
  int MIDI::ChangeTextToMidi(char *TextFileName,
  char *MidiFileName)
  { int tracks,ntrack,delaytime;
  int speed,IsFirst,nn,dd;
  unsigned char buf[80],*msgbuf,c;
  FILE *TextFp,*MidiFp;
  long FileSize;
  char SpeedVal;
  TextFp=fopen(TextFileName,”r”);
  if (TextFp==NULL)
  {sprintf(ErrorMsg,
  ”文本文件[%s]不能打开。\n”,TextFileName);
   return(TextFileNotOpen);
   }
  fseek(TextFp,0,SEEK_END); /*测试文件大小*/
  FileSize=ftell(TextFp);
  TextFileBuf=(char *)malloc(FileSize);/*为文件分配内存*/
  if (TextFileBuf==NULL)
  { sprintf(ErrorMsg,
  ”文本文件[%s]太大,没有足够的内存处理。\n”,
  TextFileName);
   fclose(TextFp);
   return(TextFileToBig);
  }
  memset(TextFileBuf,0,FileSize);
  MidiFileBuf=(char *) malloc(FileSize*4);
  if ( MidiFileBuf==NULL)
  { sprintf(ErrorMsg,”不能为MIDI文件分配内存。\n”);
  fclose(TextFp);
  free(TextFileBuf);
  return(MallocError);
  }
  MidiFp=fopen(MidiFileName,”wb”);
  if (MidiFp==NULL)
   { sprintf(ErrorMsg,
  ”Midi文件[%s]不能建立。\n”,MidiFileName);
   fclose(TextFp);
   free(MidiFileBuf);
   free(TextFileBuf);
   return(MidiFileCanNotCreate);
   }
  MidiFileOldBuf=MidiFileBuf;
  TextFileOldBuf=TextFileBuf;
  fseek(TextFp,0,SEEK_SET);
  fread(TextFileBuf,FileSize,1,TextFp);
  fclose(TextFp);
  JumpNullChar(TextFileBuf);
  c=strnicmp(TextFileBuf,”[MIDI]“,6);
  if (c)
  {sprintf(ErrorMsg,
  ”文本文件[%s]不是MIDI文本文件。\n”,MidiFileName);
  fcloseall();
  free(TextFileOldBuf);
  free(MidiFileOldBuf);
  return(NotMIDITextFile);
  }
  TextFileBuf+=6;
  JumpNullChar(TextFileBuf);
  sscanf(TextFileBuf,”%c,%d/%d,%d,%d”, //取调号等信息
  &c,&nn,&dd,&speed,&tracks);
  buf[0]=c;buf[1]=0; SetOneVal(buf); //设置该调1的键值
  if (nn<1 || nn> 7) nn=4;
  if (dd<2 || dd>16) dd=4;
  while(*TextFileBuf!=’\n’) TextFileBuf++;
  JumpNullChar(TextFileBuf);
  if (speed<60 || speed >200) speed=120;
  JumpNullChar(TextFileBuf);
  if (tracks<1 || tracks>16) tracks=1;
  JumpNullChar(TextFileBuf);
  ntrack=1;
  WriteMHToFile(6,1,tracks,speed,MidiFp);
  WriteTimeSignature(nn,dd); //设置时间记录格式
  SetPaiNum(nn);
  WriteSpeed(speed); //设置演奏速度
  while(ntrack<=tracks && *TextFileBuf!=0)
  {sprintf(buf,”[%d]“,ntrack);
  TextFileBuf=strstr(TextFileBuf,buf);//查找该磁道起始位置
  if (TextFileBuf==NULL) //没有找到
  { sprintf(ErrorMsg,
  ”在文件[%s]中,第%d磁道音乐信息没找到。\n.”,
   TextFileName,ntrack);
   free(MidiFileOldBuf);
   free(TextFileOldBuf);
   fcloseall();
   return(NotFoundTrack);
  }
  if (ntrack!=1) MidiFileBuf=MidiFileOldBuf;
  SpeedVal=0;
  TextFileBuf+=strlen(buf);
  IsFirst=1;
  while(*TextFileBuf!=0 && *TextFileBuf!=’[')
  { JumpNullChar(TextFileBuf);
  c=*(TextFileBuf++);
  if ( (c>='0' && c<='7')
  || (c>='a' && c<='g')
  || (c>='A' && c<='G')
  )
  {JumpNullChar(TextFileBuf);
   if (*TextFileBuf=='b' || *TextFileBuf=='#')
   { c=GetToneNum(c,*TextFileBuf);/*取出实际的音符*/
   TextFileBuf++;
   JumpNullChar(TextFileBuf);
   }
   else c=GetToneNum(c,' ');
  switch(*(TextFileBuf++))
   { case '-': //延长一拍
   delaytime=2*FOURPAINUM;
   JumpNullChar(TextFileBuf);
   while(*TextFileBuf=='-')
   { TextFileBuf++;
   delaytime+=FOURPAINUM;
   JumpNullChar(TextFileBuf);
   }
   break;
   case '_': //8分音符
   delaytime=FOURPAINUM/2;
   JumpNullChar(TextFileBuf);
   if(*TextFileBuf=='.')
   {TextFileBuf++;
   delaytime=delaytime*3/2;
   }
   break;
   case '=': //16分音符
   delaytime=FOURPAINUM/4;
   JumpNullChar(TextFileBuf);
   if(*TextFileBuf=='.')
   {delaytime=delaytime*3/2;
   TextFileBuf++;}
   break;
   case '.': //附点音符
   delaytime=FOURPAINUM*3/2;
   break;
   case ':': //32分音符
   delaytime=FOURPAINUM/16;
   JumpNullChar(TextFileBuf);
   if(*TextFileBuf=='.')
   {delaytime=delaytime*3/2;
   TextFileBuf++;}
   break;
   case ';': //64分音符
   delaytime=FOURPAINUM/32;
   if(*TextFileBuf=='.')
   { delaytime=delaytime*3/2;
   TextFileBuf++;}
   break;
   default:
   delaytime=FOURPAINUM;
   TextFileBuf--;
   break;
   }


   if (IsFirst)
   {WriteNoteOn(ntrack,c,
  GetCurPaiSpeed(SpeedVal/(FOURPAINUM*4/dd)+1),
  delaytime);
   IsFirst=0;}
   else
   NoteOn(c,
  GetCurPaiSpeed(SpeedVal/(FOURPAINUM*4/dd)+1),
  delaytime);
   SpeedVal=(SpeedVal+delaytime) //下一音符所处的节拍
  %(PaiNum*FOURPAINUM*4/dd);
  }
  else
  {switch(c)
  { case 'S':
  case 's':
  case 'p':
  case 'P': /*设置音色*/
   sscanf(TextFileBuf,"%d",&IsFirst);
   while(*TextFileBuf>='0' && *TextFileBuf<='9')
  TextFileBuf++;
  if (c=='P'||c=='p') //若为P,表示改变音色
   ChangePrommgram(ntrack,(char)IsFirst);
   else //否则,表示设置音量大小
   WriteSoundSize(ntrack,(unsigned int)IsFirst);
   IsFirst=1;
   break;
  case '{': /*写歌词*/
   msgbuf=buf;
   while(*TextFileBuf!='}'
   && *TextFileBuf!='\n'
   && *TextFileBuf!=0
   && *TextFileBuf!='[')
   *(msgbuf++)=*(TextFileBuf++);
   *msgbuf=0;
   IsFirst=1;
   WriteTextMsg(buf);
   if (*TextFileBuf=='}') TextFileBuf++;
   break;
  case '\\': //降八度
   OneVal-=12;
   break;
  case '/': //升八度
   OneVal+=12;
   break;
  case '[':
  case 0:
   TextFileBuf--;
   break;
  default:
   sprintf(ErrorMsg,"文本文件[%s]出现非法字符(%c)。”,
   TextFileName,c);
   free(MidiFileOldBuf);
   free(TextFileOldBuf);
   fcloseall();
   return(InvalideChar);
   }
  }
  }
  WriteTrackEndMsg(); //设置磁道结束信息
  WriteTrackMsgToFile(MidiFp); //将磁道音乐信息定入文件中
  ntrack++;
  }
  free(MidiFileOldBuf);
  free(TextFileOldBuf);
  fclose(MidiFp);
  sprintf(ErrorMsg,”MIDI文件[%s]转换成功。”,MidiFileName);
  return(ChangeOK);
  }
 /*****************************************************/
  /*作用:将长整型数据变成可变长度,存入buf处 */
  /*入口参数:n 数据 buf 结果保存入 */
  /****************************************************/
  void MIDI::WriteLenghtToBuf(unsigned long n,char *buf)
  { unsigned char b[4]={0};
  int i;
  b[3]=(unsigned char)(n&0×7f);
  i=2;
  while(n>>7)
  { n>>=7;
   b[i--]=(char)( (n&0×7f)|0×80);
   }
  for (i=0;i<4;i++)
  if (b[i]) *(buf++)=b[i];
  *buf=0;
  }
  long MIDI::NewLong(long n) //将长整型数据改成高位在前
  { union LENGHT l={0};
  char i;
  l.length=n;
  SWAP(&l.b[3],&l.b[0]);
  SWAP(&l.b[2],&l.b[1]);
  return(l.length);
  }
  //开始演奏音乐
  void MIDI::WriteNoteOn(char channel, //通道号
  char note, //音符值
  char speed, //按键速度
  unsigned long delaytime) //延时数
  { unsigned char buf[5];
  int i;
  channel–;
  *(MidiFileBuf++)=0;
  *(MidiFileBuf++)=0×90|channel&0×7f;//Write Channel
  *(MidiFileBuf++)=note;
  *(MidiFileBuf++)=speed;
  WriteLenghtToBuf(delaytime*MIDICLOCK,buf);
  i=0;
  while(buf[i]>=0×80) //Write Delay Time
   *(MidiFileBuf++)=buf[i++];
  *(MidiFileBuf++)=buf[i];
  *(MidiFileBuf++)=note;
  *(MidiFileBuf++)=0;
  }
  void MIDI::NoteOn(char note,
  char speed,
  unsigned long delaytime) //发音
  { unsigned char buf[5];
  int i;
  *(MidiFileBuf++)=0;
  *(MidiFileBuf++)=note;
  *(MidiFileBuf++)=speed;
  WriteLenghtToBuf(delaytime*MIDICLOCK,buf);
  i=0;
  while(buf[i]>0×80)
   *(MidiFileBuf++)=buf[i++];
  *(MidiFileBuf++)=buf[i];
  *(MidiFileBuf++)=note;
  *(MidiFileBuf++)=0;
  }
  void MIDI::ChangePrommgram(char channel,char n) //改变音色
  { *(MidiFileBuf++)=0;
  *(MidiFileBuf++)=0xc0|(channel-1)&0×7f;
  *(MidiFileBuf++)=n;
  }
  void MIDI::WriteTextMsg(char *msg) //向内存写入一文本信息
  { char bufmsg[100]={0xff,5,0,0,0};
  int len;
  *(MidiFileBuf++)=0;
  bufmsg[2]=(char)strlen(msg);
  strcpy(&bufmsg[3],msg);
  strcpy(MidiFileBuf,bufmsg);
  MidiFileBuf+=strlen(bufmsg)+3;
  }
  void MIDI::WriteTrackEndMsg() //磁道结束信息
  { *(MidiFileBuf++)=0;
  *(MidiFileBuf++)=0xff;
  *(MidiFileBuf++)=0×2f;
  *(MidiFileBuf++)=0;
  }
  char MIDI::GetToneNum(char n,char flag)
  /*入口参数: n 音高
   flag 升降记号
  返回值: 该乐音的实际标号值*/
  { static char val[7]={9 ,11,0,2,4,5,7};
  static char one[7]={0,2,4,5,7,9,11};
  int i;
  i=OneVal;
  if (n<=’7′&& n>=’1′) i=i+one[n-'1'];
  else
   if (n>=’a’ && n<=’g')
  i=i+val[n-'a']-12; //低音,降12个半音
   else
   if (n>=’A’ &n<=’G') //高音,升12个半音
   i=i+val[n-'A']+12;
   else //否则,识为休止符
   i=0;
  if (flag==’b') i–;
  else if (flag==’#') i++;
  return(i);
  }


   if (IsFirst)
   {WriteNoteOn(ntrack,c,
  GetCurPaiSpeed(SpeedVal/(FOURPAINUM*4/dd)+1),
  delaytime);
   IsFirst=0;}
   else
   NoteOn(c,
  GetCurPaiSpeed(SpeedVal/(FOURPAINUM*4/dd)+1),
  delaytime);
   SpeedVal=(SpeedVal+delaytime) //下一音符所处的节拍
  %(PaiNum*FOURPAINUM*4/dd);
  }
  else
  {switch(c)
  { case ‘S’:
  case ’s’:
  case ‘p’:
  case ‘P’: /*设置音色*/
   sscanf(TextFileBuf,”%d”,&IsFirst);
   while(*TextFileBuf>=’0′ && *TextFileBuf<=’9′)
  TextFileBuf++;
  if (c==’P'||c==’p') //若为P,表示改变音色
   ChangePrommgram(ntrack,(char)IsFirst);
   else //否则,表示设置音量大小
   WriteSoundSize(ntrack,(unsigned int)IsFirst);
   IsFirst=1;
   break;
  case ‘{‘: /*写歌词*/
   msgbuf=buf;
   while(*TextFileBuf!=’}’
   && *TextFileBuf!=’\n’
   && *TextFileBuf!=0
   && *TextFileBuf!=’[')
   *(msgbuf++)=*(TextFileBuf++);
   *msgbuf=0;
   IsFirst=1;
   WriteTextMsg(buf);
   if (*TextFileBuf=='}') TextFileBuf++;
   break;
  case '\\': //降八度
   OneVal-=12;
   break;
  case '/': //升八度
   OneVal+=12;
   break;
  case '[':
  case 0:
   TextFileBuf--;
   break;
  default:
   sprintf(ErrorMsg,"文本文件[%s]出现非法字符(%c)。”,
   TextFileName,c);
   free(MidiFileOldBuf);
   free(TextFileOldBuf);
   fcloseall();
   return(InvalideChar);
   }
  }
  }
  WriteTrackEndMsg(); //设置磁道结束信息
  WriteTrackMsgToFile(MidiFp); //将磁道音乐信息定入文件中
  ntrack++;
  }
  free(MidiFileOldBuf);
  free(TextFileOldBuf);
  fclose(MidiFp);
  sprintf(ErrorMsg,”MIDI文件[%s]转换成功。”,MidiFileName);
  return(ChangeOK);
  }
 /*****************************************************/
  /*作用:将长整型数据变成可变长度,存入buf处 */
  /*入口参数:n 数据 buf 结果保存入 */
  /****************************************************/
  void MIDI::WriteLenghtToBuf(unsigned long n,char *buf)
  { unsigned char b[4]={0};
  int i;
  b[3]=(unsigned char)(n&0×7f);
  i=2;
  while(n>>7)
  { n>>=7;
   b[i--]=(char)( (n&0×7f)|0×80);
   }
  for (i=0;i<4;i++)
  if (b[i]) *(buf++)=b[i];
  *buf=0;
  }
  long MIDI::NewLong(long n) //将长整型数据改成高位在前
  { union LENGHT l={0};
  char i;
  l.length=n;
  SWAP(&l.b[3],&l.b[0]);
  SWAP(&l.b[2],&l.b[1]);
  return(l.length);
  }
  //开始演奏音乐
  void MIDI::WriteNoteOn(char channel, //通道号
  char note, //音符值
  char speed, //按键速度
  unsigned long delaytime) //延时数
  { unsigned char buf[5];
  int i;
  channel–;
  *(MidiFileBuf++)=0;
  *(MidiFileBuf++)=0×90|channel&0×7f;//Write Channel
  *(MidiFileBuf++)=note;
  *(MidiFileBuf++)=speed;
  WriteLenghtToBuf(delaytime*MIDICLOCK,buf);
  i=0;
  while(buf[i]>=0×80) //Write Delay Time
   *(MidiFileBuf++)=buf[i++];
  *(MidiFileBuf++)=buf[i];
  *(MidiFileBuf++)=note;
  *(MidiFileBuf++)=0;
  }
  void MIDI::NoteOn(char note,
  char speed,
  unsigned long delaytime) //发音
  { unsigned char buf[5];
  int i;
  *(MidiFileBuf++)=0;
  *(MidiFileBuf++)=note;
  *(MidiFileBuf++)=speed;
  WriteLenghtToBuf(delaytime*MIDICLOCK,buf);
  i=0;
  while(buf[i]>0×80)
   *(MidiFileBuf++)=buf[i++];
  *(MidiFileBuf++)=buf[i];
  *(MidiFileBuf++)=note;
  *(MidiFileBuf++)=0;
  }
  void MIDI::ChangePrommgram(char channel,char n) //改变音色
  { *(MidiFileBuf++)=0;
  *(MidiFileBuf++)=0xc0|(channel-1)&0×7f;
  *(MidiFileBuf++)=n;
  }
  void MIDI::WriteTextMsg(char *msg) //向内存写入一文本信息
  { char bufmsg[100]={0xff,5,0,0,0};
  int len;
  *(MidiFileBuf++)=0;
  bufmsg[2]=(char)strlen(msg);
  strcpy(&bufmsg[3],msg);
  strcpy(MidiFileBuf,bufmsg);
  MidiFileBuf+=strlen(bufmsg)+3;
  }
  void MIDI::WriteTrackEndMsg() //磁道结束信息
  { *(MidiFileBuf++)=0;
  *(MidiFileBuf++)=0xff;
  *(MidiFileBuf++)=0×2f;
  *(MidiFileBuf++)=0;
  }
  char MIDI::GetToneNum(char n,char flag)
  /*入口参数: n 音高
   flag 升降记号
  返回值: 该乐音的实际标号值*/
  { static char val[7]={9 ,11,0,2,4,5,7};
  static char one[7]={0,2,4,5,7,9,11};
  int i;
  i=OneVal;
  if (n<=’7′&& n>=’1′) i=i+one[n-'1'];
  else
   if (n>=’a’ && n<=’g')
  i=i+val[n-'a']-12; //低音,降12个半音
   else
   if (n>=’A’ &n<=’G') //高音,升12个半音
   i=i+val[n-'A']+12;
   else //否则,识为休止符
   i=0;
  if (flag==’b') i–;
  else if (flag==’#') i++;
  return(i);
  }


 

由于本人在这方面基本没有什么尝试,只能将有些朋友们发表过的东西总结一下。这些总结中有已经实现的,有些还是在摸索中的,希望大家能根据提供的资料多试试,成功后能将自己的经验与大家分享。这些总结针对的是SP与梦网短信网关之间传输的格式,至于通过其它方式发送的格式,我也不知道能不能套用。:(


NOKIA:


铃声和图片的格式有资料详细介绍,有兴趣的最好还是去看看他的smartmessage。
在CMPP的CMPP_SUBMIT包里,其Msg_Content由两部分组成,头和内容


铃声:0×6 0×5 0×4 0×15 0×81 0 0(头) + 铃声二进制内容
图片:0×6 0×5 0×4 0×15 0×82 0 00×64 0xf0 0(头) + 图片二进制


铃声和图片的编辑工具及将文本转换成二进制文件的工具,请在论坛的“资源共享”里查找



Motorola铃声:(由iStudy提供)
在CMPP的CMPP_SUBMIT包里,其Msg_Content由|<-Header ->|<-Tempo value->|<-Musical Data -> |<-Delimiter->|Checksum->|组成


field binary data ascii data
————————————————————————————-
Header: 4c 33 35 26 –>”L35&” 
Tempo value: 32 20 –>”2 ” 
Musical Data: 43 2d 32 20 43 23 2d 32 20 44 2d 32 20 –>”C-2 C#-2 D-2 ” 
Delimiter: 26 26 –>”&&” 
Checksum: 35 38 –>”58″
————————————————————————————-
最终发送的二进制流为:4c3335263220432d322043232d3220442d322026263538


要注意的是校验位的计算,是所有Musical Data 异或后的值,然后拆分转换而来得,如果checksum计算不正确则手机接收后是些非法字符的普通消息。下面的文档中算法很详细。


Header: L35& This is a constant string for all musical tone SMS. [By using 'Send As SMS'
option user can send composed Ringer Tone to other compatible Motorola phone. To
differentiate Music SMS from Normal SMS, special header is attached.]
Tempo value: This is either 1 or 2 or 3 or 4 followed by a space(e.g. “2 “). Default is 2.
Musical Data : It consists of 1 or more notes, subject to a maximum of 35 Notes , the format
of which is given below.
Delimiter : && This represents the end of musical notes in the tone.
Checksum : Two bytes. The algorithm for calculation of the checksum is given below.


Step1: Calculate one byte XOR sum of the all the bytes of the musical data starting after
the space in the tempo value(not including space) till the && (not inclusive of &&). Let
us say this is XY.
Step 2: Separate X and Y into two bytes X0 and 0Y.
Step 3: Take 0Y and add hex 30. This becomes 3Y. Call this byte 1.
Step 4: Take X0 and shift right by 4 bits. This becomes 0X.
Step 5: Add hex 30 to 0X. This becomes 3X. Call this byte 2.
Step 6: Two byte checksum is <byte 2><byte1>



SIMENS铃声:(由hello008提供)


可能hello008还没有完全试成功,所以有兴趣的朋友还是多参见他提供的相关文档,多多测试和分析一下,成功了一定要告诉我一下喔!


在CMPP的CMPP_SUBMIT包里,其Msg_Content由
2f 2f 53 45 4f:头
1:版本
6b 0:数据段长度
7 11 9 17:类似序列号
6 0:第6个数据包
8 0:共8个数据包
f7 2 0 0:mid文件大小
3 6d 69 64:3mid
8:文件名长度 
65 31 33 39 2e 6d 69 64:文件名
。。。:数据 



ERICSSON铃声:
是我从网关上截取分析出来的,没有实际试过。
在CMPP的CMPP_SUBMIT包里,其Msg_Content由
其格式是:
binary data ascii data
———————————————————
42 45 47 49 4e 3a 45 4d 45 4c 4f 44 59 0a –>BEGIN:EMELODY+换行符(0×0a)
45 52 53 49 4f 4e 3a 31 2e 30 0a –>VERSION:1.0+换行符(0×0a)
4d 45 4c 4f 44 59 3a数据内容0a –>MELODY:+内容++换行符(0×0a)
45 4e 44 3a 45 4d 45 4c 4f 44 59 –>END:EMELODY
————————————————————-


大概就这么多吧,剩下的大家要么看文档要么试成功后把自己的经验共享一下。


在这里我并没有对CMPP_SUBMIT中同铃声和图片相关的几个字段如tp_udhi,Msg_Fmt做出说明,主要是各网关厂商对它们在处理上也不一样,如对tp_udhi,有的要求填0,有的要求填1,有的则要求填0×40。所以在调试时,最好询问一下网关的开发人员,来确定具体的值。

NOKIA、MOTOROLA、SIMENS及ERICCSON铃声和图片格式分析

由于本人在这方面基本没有什么尝试,只能将有些朋友们发表过的东西总结一下。这些总结中有已经实现的,有些还是在摸索中的,希望大家能根据提供的资料多试试,成功后能将自己的经验与大家分享。这些总结针对的是SP与梦网短信网关之间传输的格式,至于通过其它方式发送的格式,我也不知道能不能套用。:(

NOKIA:
铃声和图片的格式有资料详细介绍,有兴趣的最好还是去看看他的smartmessage。
在CMPP的CMPP_SUBMIT包里,其Msg_Content由两部分组成,头和内容

铃声:0×6 0×5 0×4 0×15 0×81 0 0(头) + 铃声二进制内容
图片:0×6 0×5 0×4 0×15 0×82 0 00×64 0xf0 0(头) + 图片二进制

铃声和图片的编辑工具及将文本转换成二进制文件的工具,请在论坛的“资源共享”里查找


Motorola铃声:(由iStudy提供)
在CMPP的CMPP_SUBMIT包里,其Msg_Content由|<-Header ->|<-Tempo value->|<-Musical Data -> |<-Delimiter->|Checksum->|组成

field binary data ascii data
————————————————————————————-
Header: 4c 33 35 26 –>”L35&”
Tempo value: 32 20 –>”2 ”
Musical Data: 43 2d 32 20 43 23 2d 32 20 44 2d 32 20 –>”C-2 C#-2 D-2 ”
Delimiter: 26 26 –>”&&”
Checksum: 35 38 –>”58″
————————————————————————————-
最终发送的二进制流为:4c3335263220432d322043232d3220442d322026263538

要注意的是校验位的计算,是所有Musical Data 异或后的值,然后拆分转换而来得,如果checksum计算不正确则手机接收后是些非法字符的普通消息。下面的文档中算法很详细。

Header: L35& This is a constant string for all musical tone SMS. [By using 'Send As SMS'
option user can send composed Ringer Tone to other compatible Motorola phone. To
differentiate Music SMS from Normal SMS, special header is attached.]
Tempo Value: This is either 1 or 2 or 3 or 4 followed by a space(e.g. “2 “). Default is 2.
Musical Data : It consists of 1 or more notes, subject to a maximum of 35 Notes , the format
of which is given below.
Delimiter : && This represents the end of musical notes in the tone.
Checksum : Two bytes. The algorithm for calculation of the checksum is given below.

Step1: Calculate one byte XOR sum of the all the bytes of the musical data starting after
the space in the tempo value(not including space) till the && (not inclusive of &&). Let
us say this is XY.
Step 2: Separate X and Y into two bytes X0 and 0Y.
Step 3: Take 0Y and add hex 30. This becomes 3Y. Call this byte 1.
Step 4: Take X0 and shift right by 4 bits. This becomes 0X.
Step 5: Add hex 30 to 0X. This becomes 3X. Call this byte 2.
Step 6: Two byte checksum is <byte 2><byte1>


SIMENS铃声:(由hello008提供)
可能hello008还没有完全试成功,所以有兴趣的朋友还是多参见他提供的相关文档,多多测试和分析一下,成功了一定要告诉我一下喔!
在CMPP的CMPP_SUBMIT包里,其Msg_Content由
2f 2f 53 45 4f:头
1:版本
6b 0:数据段长度
7 11 9 17:类似序列号
6 0:第6个数据包
8 0:共8个数据包
f7 2 0 0:mid文件大小
3 6d 69 64:3mid
8:文件名长度
65 31 33 39 2e 6d 69 64:文件名
。。。:数据


ERICSSON铃声:
是我从网关上截取分析出来的,没有实际试过。
在CMPP的CMPP_SUBMIT包里,其Msg_Content由
其格式是:
binary data ascii data
———————————————————
42 45 47 49 4e 3a 45 4d 45 4c 4f 44 59 0a –>BEGIN:EMELODY+换行符(0×0a)
45 52 53 49 4f 4e 3a 31 2e 30 0a –>VERSION:1.0+换行符(0×0a)
4d 45 4c 4f 44 59 3a数据内容0a –>MELODY:+内容++换行符(0×0a)
45 4e 44 3a 45 4d 45 4c 4f 44 59 –>END:EMELODY
————————————————————-

大概就这么多吧,剩下的大家要么看文档要么试成功后把自己的经验共享一下。
在这里我并没有对CMPP_SUBMIT中同铃声和图片相关的几个字段如tp_udhi,Msg_Fmt做出说明,主要是各网关厂商对它们在处理上也不一样,如对tp_udhi,有的要求填0,有的要求填1,有的则要求填0×40。所以在调试时,最好询问一下网关的开发人员,来确定具体的值。

手机铃声和图片的详细设计说明

这篇文档将前些日子自己测试的过程写下来供大家参考,可能现在这些东西许多人已经不再做了,但为了那些还在为这些东西摸索的朋友提供方便我还是写出来,但因为时间和资料的原因,所涉及到的还不是很全,希望知道更多细节的朋友们能与我分享你们在这方面的经验与知识。我将尽可能详细地写出它们制作的过程,包括测试用的手机型号,使用的工具,相关的算法,SP发送参数的设置等。

一. MOTOROLA普通铃声
1) 测试环境:手机型号–A388、T191;工具–NOK2PHONE;网关–亚信
2) 制作过程:以”999朵玫瑰”为例,首先用NOK2PHONE打开相应的MIDI文件,在NOK2PHONE的输出框中产生MOTOROLA普通铃声所需的字串,如” 2 F2 E2 C2 D4 R2 A-1 G-1 F-2 G-2 C2 A-6 A-4 C2 A-2 G-6 D1 D1 C2 D2 E2”,根据算法计算出真正所需发送的铃声,内容是” L35&2 2F2E2C2D4R2A-1G-1F-2G-2C2A-6A-4C2A-2G-6D1D1C2D2E2&&27”,通过SP程序将该字串做为普通的文本消息发送至手机,手机上显示收到铃声。
3) 算法:MOTOROLA的铃声格式如下:
<Header><Tempo Value><Musical Data><Delimiter><Checksum>
—————————————————————————————–
Header:L35&
Tempo Value:由1或2或3或4加空格构成,缺省为2,如”2 “
Musical Data:将NOK2PHONE中产生的字串去掉空格即可
Delimiter:&&
Checksum:对Musical Data进行的校验和,两个字节。方法为对Musical Data中的数据从前往后对每个字符进行异或运算,运算的结果假设其十六进制的表示为XY,那Checksum的第一个字节的十六进制形式为3Y,第二个字节的十六进制形式为3X。

部分算法:
BYTE a = 0, check1, check2;
CString sRead(_T(“2 F2 E2 C2 D4 R2 A-1 G-1 F-2 G-2 C2 A-6 A-4 C2 A-2 G-6 D1 D1 C2 D2 E2”)), sRing;

sRead.Replace(” “,”");
arrayMessage.SetSize(sRead.GetLength() + 1);
strcpy((char *)arrayMessage.GetData(), sRead);
for(int i=0;i<arrayMessage.GetSize()-1;i++)
{
a = a ^ arrayMessage.GetAt(i);
}

check1 = 0×30 + (a & 0xf);
check2 = 0×30 + (a>>4 & 0xf) ;

sRing.Format(“L35&2 %s&&%c%c”,(char *)arrayMessage.GetData(),check2,check1);
4) 发送参数设置:该消息是以普通文本方式的消息下发,ftm=0,udhi=0,pid=0,如果Checksum不对,则该消息以文本方式显示在手机上,否则显示为铃声。

二. SIEMENS图片(适用于其支持的各种大小的图片)
1) 测试环境:手机型号–3118;网关–亚信
2) 制作过程:首先选择一张101X29的单色BMP图做为源文件,文件名为1.BMP。通过程序的处理源文件被拆成4条连续的消息以二进制的形式存放在文件1.PIC中,发送时依次取出来发送即可。第一条和第四条消息的十六进制内容如下:
第一条:2F 2F 53 45 4F 01 6E 00 DD 05 00 00 01 00 04 00 8E 01 00 00
03 62 6D 70 05 31 2E 62 6D 70 42 4D 8E 01 00 00 00 00 00 00
3E 00 00 00 28 00 00 00 48 00 00 00 1C 00 00 00 01 00 01 00
00 00 00 00 50 01 00 00 00 00 00 00 00 00 00 00 02 00 00 00
02 00 00 00 FF FF FF 00 00 00 00 00 C0 3F 00 00 00 10 EB 00
00 00 00 00 E0 3F 00 FC 00 21 DB 00 00 00 00 00 E0 3E 03 03
00 71 F3 00 00 00 00 00 E0 3E 04 00 80 0D FE 00 00 00 00 00
…….
第四条:2F 2F 53 45 4F 01 6E 00 DD 05 00 00 04 00 04 00 8E 01 00 00
03 62 6D 70 05 31 2E 62 6D 70 0E 18 00 DB E0 00 00 00 0F E7
FF FC 1C 10 00 71 C0 00 00 00 07 FF BE 00 3C 70 00 00 00 00
00 00 07 EF FC 00 DF E0 00 00 00 00 00 00 05 EC FF 00 FB C0
00 00 00 00 00 00 02 66 DF 00 BF 30 00 00 00 00 00 00
3) 算法:其图片格式如下:
<Identifier><Version><DataSize><ReferenceID><ActPacketNumber><NumberOfPackets><ObjectSize> <ObjectType><ObjectName><Data>
—————————————————————————–
Identifier:标识。5字节。设为”//SEO”(注意大小写)
Version:版本号。1字节。设为1
DataSize:数据大小。2字节。每个包里可图片数据的大小,对于同一图片的不同包的该值相同,最后一个包里实际剩余数据大小不足时,添0补足
ReferenceID:序列号。4字节。可自行设定,对同一图片的不同包的该值相同
ActPacketNumber:当前包是第几包。2字节。从1开始计数
NumberOfPackets:总包数。2字节。
ObjectSize:图片的大小。4字节。
ObjectType:类型为图片。4字节。第一字节为长度,因为类型为”bmp”所以第一字节为3,第二至第四字节为”bmp”
ObjectName:图片的名称,包括扩展名。长度不定。第一个字节为长度。如图片为1.bmp,则ObjectName的长度为6,第一个字节为5。第二至六个字节为”1.bmp”
Data:图片数据。从图片文件中拆分出来的数据。

2004年11月08日

总参

2004年10月25日

赴宴是交际者经常性的活动之一,其中有许多值得注意的礼节。赴宴前,应注意仪表整洁,穿戴大方,最好稍作打扮。忌穿工作服,满脸倦容或一身灰尘。为此,进行一番洗理一番化妆是很有必要的。男士要刮净胡须,如有时间还应理发。注意鞋子是否干净、光亮,袜子是否有臭味,以免临时尴尬。

  赴宴要遵守约定的时间,既不要太早,显得急于进餐,也不能迟到。最好事先探询一下,可依据请柬注明的时间,稍微提前一点。如果你与主人关系密切,则不妨早点到达,以帮助主人招待宾客,或做些准备工作。

  当你抵达宴请地点时,首先跟主人握手、问候致意。对其他客人,无论相识与否,都要笑脸相迎,点头致意,或握手寒暄,互相问好;对长辈老人,要主动让座请安;对小孩则应多加关照。万一迟到,在你坐下之前,应先向所有客人微笑打招呼,同时说声抱歉。

狗咬伤,不管是疯狗,还是正常狗,都应以最快迅速,就地用大量清水冲洗伤口。

  若周围一时无水源,那么可先用人尿代替清水冲洗,然后再设法找水


  冲洗伤口要彻底。狗咬伤的伤口往往是外口小里面深,这就要求冲洗的时候尽可能把伤口扩大,并用力挤压周围软组织,设法把沾污在伤口上狗的唾液和伤口上的血液冲洗干净。

  若伤口出血过多,应设法立即上止血带,然后再送医院急救。

  记住:不要包扎伤口!

  就地、立即、彻底冲洗伤口,是决定抢救成败的关键,

  切不可忘了冲洗伤口,或者马马虎虎冲洗一下,甚至涂点红汞包扎好伤口就上医院,这是绝对错误的。