home *** CD-ROM | disk | FTP | other *** search
- /* Record.c
- Copyright (c) 1990,1991,1992,1993 by Thomas E. Janzen
- All Rights Reserved
-
- THIS SOFTWARE IS FURNISHED FREE OF CHARGE FOR STUDY AND USE AND MAY
- BE COPIED ONLY FOR PERSONAL USE OR COMPLETELY AS OFFERED WITH NO
- CHANGES FOR FREE DISTRIBUTION. NO TITLE TO AND OWNERSHIP OF THE
- SOFTWARE IS HEREBY TRANSFERRED. THOMAS E. JANZEN ASSUMES NO
- RESPONSIBILITY FOR THE USE OR RELIABILITY OF THIS SOFTWARE.
-
- Thomas E. Janzen
- 208A Olde Derby Road
- Norwood, MA 02062-1761
- (617)769-7733
-
- ** FACILITY:
- **
- ** AlgoRhythms music improviser on Commodore (TM) Amiga (TM)
- ** compiled with SAS/C Amiga Compiler 6.50
- **
- ** ABSTRACT:
- **
- ** Record.c contains functions for MIDI event recording and saving
- **
- ** AUTHORS: Thomas E. Janzen
- **
- ** CREATION DATE: 9-DEC-1991
- **
- ** MODIFICATION HISTORY:
- ** DATE NAME DESCRIPTION
- ** 4 Jan 92 TEJ last changes for 2.0
- */
-
- #include <stdlib.h>
- #include <stdio.h>
- #include <intuition/intuition.h>
- #include <proto/timer.h>
- #include <ctype.h>
- #include <string.h>
- #include "AlgoRhythms.h"
- #include "Menus.h"
- #include "record.h"
-
- #define NOTES_PER_BLOCK 1024
- #define MULTITRAK 1
- #define MIDIHDRLEN 14
- #define MIDITRKHDRLEN 8
- #define TICKS_PER_QUARTER 240
- #define NOTEON 0X90
- #define META_EVENT 0XFF
- #define TEXT_EVENT 0X01
- #define TRACK_NAME 0X03
- #define EOT_EVENT 0X2F
- #define TEMPO_EVENT 0X51
- #define TIME_SIG_EVENT 0X58
- #define PACK_BYTES(A, B, C, D) (D | (C << 8) | (B << 16) | (A << 24))
-
- struct Note_Record_Struct
- {
- struct timeval nr_r_event_time;
- unsigned char nr_uc_channel,
- nr_uc_pitch,
- nr_uc_velocity;
- };
- typedef struct Note_Record_Struct NOTE_RECORD_TYPE;
-
- typedef struct Page_List_Struct PAGE_LIST_TYPE;
- struct Page_List_Struct
- {
- PAGE_LIST_TYPE *pl_r_succ,
- *pl_r_pred;
- NOTE_RECORD_TYPE *pl_r_page;
- };
-
- typedef struct midi_note_struct MIDI_NOTE_TYPE;
- struct midi_note_struct
- {
- unsigned char mn_uc_sts_chanl,
- mn_uc_note_num,
- nm_velocity;
- };
-
- typedef struct MIDI_Header MIDI_HEADER_TYPE;
- struct MIDI_Header
- {
- unsigned int chunk_type,
- chunk_len;
- unsigned short int format,
- num_tracks,
- division; /* of time, usu. 24 MIDI ticks */
- };
-
- typedef struct MIDI_Track MIDI_TRACK_TYPE;
- struct MIDI_Track
- {
- unsigned int chunk_type,
- chunk_len;
- };
-
- static unsigned char *midi_score = NULL;
- static unsigned int total_notes_qty = 0;
-
- static PAGE_LIST_TYPE page_list_head = {NULL, NULL, NULL};
- static PAGE_LIST_TYPE *current_list_ptr = NULL;
- unsigned int recording = FALSE;
- static int page_note_counter = 0;
-
- static void add_page(void);
-
- static int variable_len_values(int number, unsigned char *array);
- static void insert_meta_event(unsigned int *midi_index,
- unsigned char *midi_score,
- const unsigned char event_type,
- const unsigned int event_len);
- static void store_a_note( unsigned int *midi_index,
- unsigned char *midi_score,
- unsigned char current_channel,
- unsigned char current_dynamic,
- unsigned char pitch,
- unsigned char *running_sts);
-
- unsigned int record_init(void)
- /*
- ** FUNCTIONAL DESCRIPTION:
- ** Sets up the recording data structures
- **
- ** RETURN VALUE:
- ** description:
- ** data_type: int
- **
- ** DESIGN:
- **
- ** ROUTINE
- ** : IF page_list_head.pl_r_page == NULL
- ** : : page_list_head.pl_r_page = malloc()
- ** : : IF malloc failed
- ** : : : return FALSE
- ** : : ENDIF
- ** : : initialize list head pred and succ to NULL
- ** : : current_list_ptr = &page_list_head
- ** : : total_notes_qty = 0
- ** : ENDIF
- ** : return TRUE
- ** ENDROUTINE
- **
- */
- {
- if (NULL == page_list_head.pl_r_page)
- {
- page_list_head.pl_r_page
- = malloc(sizeof(NOTE_RECORD_TYPE) * NOTES_PER_BLOCK);
- if (NULL == page_list_head.pl_r_page)
- {
- return FALSE; /* recording won't turn on if malloc failed */
- }
- page_list_head.pl_r_succ = NULL;
- page_list_head.pl_r_pred = NULL;
- current_list_ptr = &page_list_head;
- total_notes_qty = 0;
- page_note_counter = 0;
- }
- return TRUE;
- }
-
- int record_note_event(NOTE_EVENT_TYPE *note_event)
- /*
- ** FUNCTIONAL DESCRIPTION:
- ** Stores a play note event in memory for later writing to a MIDI file
- ** RETURN VALUE:
- ** description: TRUE if successful
- ** data_type: int
- **
- ** ARGUMENTS:
- **
- ** note_event-
- ** description: a single voice
- ** data_type: pointer to NOTE_EVENT_TYPE
- ** access: read/write only
- **
- ** DESIGN:
- **
- ** ROUTINE record_note_event
- ** : IF current_list_ptr->pl_r_page == NULL
- ** : : current_list_ptr->pl_r_page = malloc()
- ** : : IF malloc failed
- ** : : : turn off recording
- ** : : : clear_record()
- ** : : : return FALSE
- ** : : ENDIF
- ** : ENDIF
- ** : IF this block of notes is full
- ** : : add_page()
- ** : : clear note_event_index
- ** : ENDIF
- ** : copy pitch, channel and dynamic from note_event to note_record
- ** : IF note is playing
- ** : : copy note_event start_time to note_record event_time
- ** : ELSE the note is being turned off
- ** : : copy note_event stop_time to note_record event_time
- ** : ENDIF
- ** : append note_record into the data array pointed to by linked list
- ** : increment note_event_index
- ** : increment notes_qty
- ** : return TRUE
- ** ENDROUTINE
- **
- */
- {
- auto NOTE_RECORD_TYPE note_record;
-
- if (NULL == current_list_ptr->pl_r_page)
- {
- current_list_ptr->pl_r_page
- = malloc(sizeof(NOTE_RECORD_TYPE) * NOTES_PER_BLOCK);
- if (NULL == current_list_ptr->pl_r_page)
- {
- recording = FALSE;
- clear_record();
- /* post an error message */
- return FALSE;
- }
- }
- if (page_note_counter >= NOTES_PER_BLOCK)
- {
- add_page();
- }
- note_record.nr_uc_pitch = note_event->nv_i_cur_pitch;
- note_record.nr_uc_channel = note_event->nv_i_channel;
- note_record.nr_uc_velocity = note_event->nv_i_dynamic;
- if (note_event->nv_i_playing) /* it's a note on */
- {
- note_record.nr_r_event_time = note_event->nv_r_start_time;
- }
- else
- {
- note_record.nr_r_event_time = note_event->nv_r_stop_time;
- }
- memcpy(&(current_list_ptr->pl_r_page[page_note_counter]),
- ¬e_record, sizeof(NOTE_RECORD_TYPE));
- page_note_counter++;
- total_notes_qty++;
- return TRUE;
- }
-
- static void add_page(void)
- /*
- ** FUNCTIONAL DESCRIPTION:
- ** appends another page for MIDI data to linked list
- **
- ** DESIGN:
- **
- ** ROUTINE add_page
- ** : current_list_ptr->pl_r_succ = malloc()
- ** : IF malloc failed
- ** : : stop recording
- ** : : clear_record()
- ** : : return
- ** : ENDIF
- ** : current_list_ptr->pl_r_succ->pl_r_pred = current_list_ptr
- ** : current_list_ptr = current_list_ptr->pl_r_succ
- ** : current_list_ptr->pl_r_page = malloc(page)
- ** : IF malloc failed
- ** : : stop recording
- ** : : clear_record()
- ** : : return
- ** : ENDIF
- ** : current_list_ptr->pl_r_succ = NULL
- ** : return
- ** ENDROUTINE
- **
- */
- {
- current_list_ptr->pl_r_succ = malloc(sizeof(PAGE_LIST_TYPE));
- if (NULL == current_list_ptr->pl_r_succ)
- {
- recording = FALSE;
- clear_record();
- return;
- }
- current_list_ptr->pl_r_succ->pl_r_pred = current_list_ptr;
- current_list_ptr = current_list_ptr->pl_r_succ;
- current_list_ptr->pl_r_page
- = malloc(sizeof(NOTE_RECORD_TYPE) * NOTES_PER_BLOCK);
- if (NULL == current_list_ptr->pl_r_page)
- {
- recording = FALSE;
- clear_record();
- /* post an error message */
- return;
- }
- current_list_ptr->pl_r_succ = NULL;
- page_note_counter = 0;
- return;
- }
-
- void erase_recording(void)
- /*
- ** FUNCTIONAL DESCRIPTION:
- ** Dissassembles the linked list of recorded MIDI data.
- **
- ** DESIGN:
- **
- ** ROUTINE erase_recording
- ** : tmp_entry = &page_list_head
- ** : WHILE not at end of page list
- ** : : tmp_entry = tmp_entry->pl_r_succ
- ** : ENDWHILE
- ** : WHILE not at beginning of list
- ** : : free page of this entry
- ** : : step back one list entry
- ** : : free successor of this list entry
- ** : ENDWHILE
- ** : zero out total_notes_qty
- ** ENDROUTINE
- **
- */
- {
- auto PAGE_LIST_TYPE *tmp_entry;
-
- tmp_entry = &page_list_head;
-
- while (tmp_entry->pl_r_succ != NULL)
- {
- tmp_entry = tmp_entry->pl_r_succ;
- }
- /*
- ** we are now pointing at the last list entry
- ** so unwind the data
- */
- while (tmp_entry->pl_r_pred != NULL)
- {
- free(tmp_entry->pl_r_page);
- tmp_entry->pl_r_page = NULL;
- tmp_entry = tmp_entry->pl_r_pred;
- free(tmp_entry->pl_r_succ);
- tmp_entry->pl_r_succ = NULL;
- }
- total_notes_qty = 0;
- /*
- ** We should now point to the head of the list
- */
- return;
- }
-
- int write_midi(char *midi_file_nam)
- /*
- ** FUNCTIONAL DESCRIPTION:
- ** Write the recording buffer to a MIDI file
- **
- ** RETURN VALUE:
- ** description: TRUE if successful
- ** data_type: int
- **
- ** ARGUMENTS:
- **
- ** midi_file_nam-
- ** description: name of MIDI file to which to write the record buff
- ** data_type: pointer to char
- ** access: read/write only
- **
- ** DESIGN:
- **
- ** ROUTINE write_midi
- ** : local_list_ptr = &page_list_head
- ** : midi_index = 0
- ** : midi_score = malloc(room for all of the notes and then some)
- ** : IF malloc failed
- ** : : return FALSE
- ** : ENDIF
- ** : midi_score[midi_index] = 0
- ** : midi_index++
- ** : insert track name meta event into midi_score
- ** : append 0 delay byte to midi_score
- ** : append tempo meta event to midi_score
- ** : append 0 delay byte to midi_score
- ** : append 4/4 meter meta event to midi_score
- ** : FOR all the recorded notes
- ** : : IF ran out of notes in this block
- ** : : : go to next block
- ** : : : IF this list member is NULL OR its page pointer is NULL
- ** : : : : free midi_score
- ** : : : : return FALSE
- ** : : : ENDIF
- ** : : : intra_block_index = 0
- ** : : ENDIF
- ** : : copy note_record from the linked list page
- ** : : increment intra_block_index
- ** : : copy pitch, channel and velocity from note_record to note_event
- ** : : tmp_time = note_record event_time
- ** : : IF local_total_notes_qty == 0
- ** : : : last_time = tmp_time
- ** : : ENDIF
- ** : : IF tmp_time > last_time
- ** : : : fix last_time and time_int
- ** : : ELSE
- ** : : : tmp_time = tmp_time - last_time
- ** : : : last_time = note_record event_time
- ** : : : convert tmp_time into MIDI delay
- ** : : ENDIF
- ** : : store the note in midi_score
- ** : ENDFOR
- ** : add a trailing delay
- ** : increment midi_index
- ** : add end of track MIDI meta event in midi_score
- ** : open midi file
- ** : IF file open failed
- ** : : free midi_score
- ** : : return FALSE
- ** : ENDIF
- ** : write MIDI header to the midi file
- ** : write track type chunk to file
- ** : write midi_score to file
- ** : flush and close file
- ** : free midi_score
- ** : return TRUE
- ** ENDROUTINE
- **
- */
- {
- auto unsigned char filemode[2] = "w",
- running_sts = 0,
- track_nam[32] = "AlgoRhythms by Tom Janzen";
- auto unsigned int midi_index = 0,
- local_total_notes_qty = 0,
- intra_block_index = 0,
- time_int,
- time_len;
- auto struct timeval tmp_time = {0, 0},
- last_time = {0, 0};
- auto FILE *midi_file = NULL;
- auto NOTE_RECORD_TYPE note_record;
- auto MIDI_NOTE_TYPE note_event;
- auto PAGE_LIST_TYPE *local_list_ptr = NULL;
- auto MIDI_HEADER_TYPE midi_file_hdr =
- {PACK_BYTES ('M','T','h','d'), 6,
- MULTITRAK, 1, TICKS_PER_QUARTER};
- auto MIDI_TRACK_TYPE track = {PACK_BYTES ('M', 'T', 'r', 'k'), 0};
-
- local_list_ptr = &page_list_head; /* initialize list pointer */
- midi_index = 0;
- /*
- ** can't be larger than * 8 + header;
- ** note = 4 bytes delay max, 3 for event max
- */
- midi_score = malloc((total_notes_qty * 10) + 18 + 4);
- if (NULL == midi_score)
- {
- return FALSE;
- }
- midi_score[midi_index] = 0;
- midi_index++;
- insert_meta_event(&midi_index, midi_score, TRACK_NAME,
- strlen(track_nam));
- strcpy(&midi_score[midi_index], track_nam);
- midi_index += strlen(track_nam);
-
- midi_score[midi_index] = 0;
- midi_index++;
-
- insert_meta_event(&midi_index, midi_score, TEMPO_EVENT, 3);
- midi_score[midi_index] = 0x0F;
- midi_index++;
- midi_score[midi_index] = 0x42;
- midi_index++;
- midi_score[midi_index] = 0x40; /* these 3 numbers give
- ** 1 million ticks/quarter note */
- midi_index++;
- midi_score[midi_index] = 0; /* required zero delay */
-
- midi_index++;
- insert_meta_event(&midi_index, midi_score, TIME_SIG_EVENT, 4);
- midi_score[midi_index] = 4;
- midi_index++;
- midi_score[midi_index] = 2;
- midi_index++;
- midi_score[midi_index] = 24;
- midi_index++;
- midi_score[midi_index] = 8;
- midi_index++;
- for ( local_total_notes_qty = 0;
- local_total_notes_qty < total_notes_qty;
- local_total_notes_qty++)
- {
- if (intra_block_index >= NOTES_PER_BLOCK)
- {
- local_list_ptr = local_list_ptr->pl_r_succ;
- if ((NULL == local_list_ptr)
- || (NULL == local_list_ptr->pl_r_page))
- {
- free(midi_score);
- midi_score = NULL;
- return FALSE; /* post error */
- }
- intra_block_index = 0;
- }
- memcpy(¬e_record,
- &(local_list_ptr->pl_r_page[intra_block_index]),
- sizeof(NOTE_RECORD_TYPE));
- intra_block_index++;
- note_event.mn_uc_note_num = note_record.nr_uc_pitch;
- note_event.mn_uc_sts_chanl = NOTEON | note_record.nr_uc_channel;
- note_event.nm_velocity = note_record.nr_uc_velocity;
- /*
- ** convert absolute time struct to MIDI delay bytes
- */
- tmp_time = note_record.nr_r_event_time;
- if (0 == local_total_notes_qty)
- {
- last_time = tmp_time;
- }
- if (1 == CmpTime(&tmp_time, &last_time)) /* -1, first > second */
- /*
- ** The new time can be earlier than the last time if the user
- ** stopped the music and hit "Play" i.e., from the beginning
- */
- {
- last_time = tmp_time;
- time_int = 0;
- }
- else
- {
- SubTime(&tmp_time, &last_time); /* f(a, b), a = a - b */
- last_time = note_record.nr_r_event_time;
- time_int
- = (tmp_time.tv_micro / 1000) + (tmp_time.tv_secs * 1000);
- time_int = (time_int * 240) / 1000; /* give 240/second */
- }
- time_len
- = variable_len_values(time_int, &midi_score[midi_index]);
- midi_index += time_len;
- store_a_note(&midi_index, midi_score, note_event.mn_uc_sts_chanl,
- note_event.nm_velocity, note_event.mn_uc_note_num,
- &running_sts);
- }
- midi_score[midi_index] = 0;
- midi_index++;
- insert_meta_event(&midi_index, midi_score, EOT_EVENT, 0);
- midi_file = fopen(midi_file_nam, filemode);
- if (NULL == midi_file)
- {
- free(midi_score);
- midi_score = NULL;
- /* report an error */
- return FALSE;
- }
- fwrite((char *)&midi_file_hdr,sizeof(MIDI_HEADER_TYPE), 1, midi_file);
- track.chunk_len = midi_index;
- fwrite((char *)&track, sizeof(MIDI_TRACK_TYPE), 1, midi_file);
- fwrite((char *)midi_score, midi_index, 1, midi_file);
- fflush(midi_file);
- fclose(midi_file);
- free(midi_score);
- midi_score = NULL;
-
- return TRUE;
- }
-
- static int variable_len_values(int number, unsigned char *array)
- /*
- ** FUNCTIONAL DESCRIPTION:
- ** Converts an integer to a variable-length MIDI multi-byte value
- **
- ** RETURN VALUE:
- ** description: number of bytes in variable-length MIDI value
- ** data_type: int
- **
- ** ARGUMENTS:
- **
- ** number-
- ** description: number to convert to multi-byte MIDI file value
- ** data_type: int
- ** access: read only
- **
- ** array-
- ** description: The array into which to store the multi-byte value
- ** data_type: pointer to char
- ** access: write only
- **
- ** DESIGN:
- **
- ** ROUTINE variable_len_values
- ** : ls_byte = number & x7F
- ** : mid_byte = number & x3F80 >> 7 | x80
- ** : ms_byte = number & x1FC000 >> 14 | x80
- ** : byte_4 = number & xFE00000 >> 21 | x80
- ** : IF byte_4 had a number in it
- ** : : array[0] = byte_4
- ** : : array[1] = ms_byte
- ** : : array[2] = mid_byte
- ** : : array[3] = ls_byte
- ** : : return 4
- ** : ENDIF
- ** : IF ms_byte had a value in it
- ** : : array[0] = mid_byte
- ** : : array[1] = ms_byte
- ** : : array[2] = ls_byte
- ** : : return 3
- ** : ENDIF
- ** : IF mid_byte had a value in it
- ** : : array[0] = mid_byte
- ** : : array[1] = ls_byte
- ** : : return 2
- ** : ENDIF
- ** : array[0] = ls_byte
- ** : return 1
- ** ENDROUTINE
- */
- {
- auto unsigned char byte_4,
- ms_byte,
- mid_byte,
- ls_byte;
-
- ls_byte = (number & 0x0000007F);
- mid_byte = ((number & 0x00003F80) >> 7) | 0x80;
- ms_byte = ((number & 0x001FC000) >> 14) | 0x80;
- byte_4 = ((number & 0x0FE00000) >> 21) | 0x80;
- if (byte_4 != 0x80)
- {
- array[0] = byte_4;
- array[1] = ms_byte;
- array[2] = mid_byte;
- array[3] = ls_byte;
- return 4;
- }
- if (ms_byte != 0x80)
- {
- array[0] = ms_byte;
- array[1] = mid_byte;
- array[2] = ls_byte;
- return 3;
- }
-
- if (mid_byte != 0x80)
- {
- array[0] = mid_byte;
- array[1] = ls_byte;
- return 2;
- }
- array[0] = ls_byte;
-
- return 1;
- }
-
- static void insert_meta_event(unsigned int *midi_index,
- unsigned char *midi_score,
- const unsigned char event_type,
- const unsigned int event_len)
- /*
- ** FUNCTIONAL DESCRIPTION:
- ** Adds a meta event to a MIDI score
- **
- ** ARGUMENTS:
- **
- ** midi_index-
- ** description: position in midi_score area
- ** data_type: pointer to unsigned int
- ** access: read/write
- **
- ** midi_score-
- ** description: midi score area
- ** data_type: pointer to unsigned char
- ** access: write only
- **
- ** event_type-
- ** description: type of meta event being inserted
- ** data_type: char
- ** access: read only
- **
- ** event_len-
- ** description: length of meta event
- ** data_type: unsigned int
- ** access: read only
- **
- ** DESIGN:
- **
- ** ROUTINE insert_meta_event
- ** : midi_score[*midi_index] = META_EVENT marker
- ** : increment midi_index
- ** : midi_score[*midi_index] = event_type
- ** : increment midi_index
- ** : midi_score[*midi_index] = event_len
- ** : increment midi_index
- ** ENDROUTINE
- */
- {
- midi_score[*midi_index] = META_EVENT;
- (*midi_index)++;
- midi_score[*midi_index] = event_type;
- (*midi_index)++;
- midi_score[*midi_index] = event_len;
- (*midi_index)++;
-
- return;
- }
-
- static void store_a_note( unsigned int *midi_index,
- unsigned char *midi_score,
- unsigned char current_channel,
- unsigned char current_dynamic,
- unsigned char pitch,
- unsigned char *running_sts)
- /*
- ** FUNCTIONAL DESCRIPTION:
- ** Put a note event into midi_score
- **
- ** ARGUMENTS:
- **
- ** midi_index-
- ** description: position in internal MIDI score array
- ** data_type: pointer to int
- ** access: read/write
- **
- ** midi_score-
- ** description: internal MIDI score array for recording
- ** data_type: pointer to char
- ** access: write only
- **
- ** current_channel-
- ** description: MIDI channel
- ** data_type: unsigned char
- ** access: read only
- **
- ** current_dynamic-
- ** description: MIDI dynamic
- ** data_type: unsigned char
- ** access: read only
- **
- ** pitch-
- ** description: MIDI pitch
- ** data_type: unsigned char
- ** access: read only
- **
- ** running_sts-
- ** description: current running status
- ** data_type: pointer to unsigned char
- ** access: read/write
- **
- ** DESIGN:
- **
- ** ROUTINE store_a_note
- ** : copy channel, pitch, dynamic to midi_note
- ** : IF chanl == file running status
- ** : : copy only pitch and dynamic into midi_score
- ** : ELSE
- ** : : copy channel/noteon, pitch and dynamic into midi_score
- ** : : set new running status for file
- ** : ENDIF
- ** ENDROUTINE
- */
- {
- static MIDI_NOTE_TYPE midi_note;
-
- midi_note.mn_uc_sts_chanl = current_channel;
- midi_note.mn_uc_note_num = pitch;
- midi_note.nm_velocity = current_dynamic;
- if (midi_note.mn_uc_sts_chanl == *running_sts)
- {
- memcpy( &midi_score[*midi_index], &(midi_note.mn_uc_note_num), 2);
- (*midi_index) += 2;
- }
- else /* running status changed */
- {
- memcpy(&midi_score[*midi_index], &midi_note, sizeof midi_note);
- (*midi_index) += sizeof(MIDI_NOTE_TYPE);
- *running_sts = midi_note.mn_uc_sts_chanl;
- }
- return;
- }
-