コード例 #1
0
static autoTextGrid SpeechSynthesizer_and_Sound_and_IntervalTier_align2 (SpeechSynthesizer me, Sound thee, IntervalTier him, long istart, long iend, double silenceThreshold, double minSilenceDuration, double minSoundingDuration, double trimDuration) {
    try {
        if (istart < 1 || iend < istart || iend > his intervals.size) {
            Melder_throw (U"Not avalid interval range.");
        }
        OrderedOf<structTextGrid> textgrids;
        TextInterval tb = his intervals.at [istart];
        TextInterval te = his intervals.at [iend];
        autoTextGrid result = TextGrid_create (tb -> xmin, te -> xmax, U"sentence clause word phoneme", U"");
        for (long iint = istart; iint <= iend; iint ++) {
            TextInterval ti = his intervals.at [iint];
            if (ti -> text && str32len (ti -> text) > 0) {
                autoSound sound = Sound_extractPart (thee, ti -> xmin, ti -> xmax,  kSound_windowShape_RECTANGULAR, 1, true);
                autoTextGrid grid = SpeechSynthesizer_and_Sound_and_TextInterval_align2 (me, sound.peek(), ti, silenceThreshold, minSilenceDuration, minSoundingDuration, trimDuration);
                textgrids. addItem_move (grid.move());
            }
        }
        if (textgrids.size == 0) {
            Melder_throw (U"Nothing could be aligned. Was your IntervalTier empty?");
        }
        autoTextGrid aligned = TextGrids_to_TextGrid_appendContinuous (& textgrids, true);
        return aligned;
    } catch (MelderError) {
        Melder_throw (U"No aligned TextGrid created.");
    }
}
コード例 #2
0
TextGrid TextGrid_readFromTIMITLabelFile (MelderFile file, int phnFile) {
	try {
		double dt = 1.0 / 16000; /* 1 / (TIMIT samplingFrequency) */
		double xmax = dt;
		autofile f = Melder_fopen (file, "r");

		// Ending time will only be known after all labels have been read.
		// We start with a sufficiently long duration (one hour) and correct this later.

		autoTextGrid me = TextGrid_create (0, 3600, U"wrd", 0);
		IntervalTier timit = (IntervalTier) my tiers -> item[1];
		long linesRead = 0;
		char line[200], label[200];
		while (fgets (line, 199, f)) {
			long it1, it2;
			linesRead++;
			if (sscanf (line, "%ld%ld%s", &it1, &it2, label) != 3) {
				Melder_throw (U"Incorrect number of items.");
			}
			if (it1 < 0 || it2 <= it1) {
				Melder_throw (U"Incorrect time at line ", linesRead);
			}
			xmax = it2 * dt;
			double xmin = it1 * dt;
			long ni = timit -> intervals -> size - 1;
			if (ni < 1) {
				ni = 1;
				// Some files do not start with a first line "0 <number2> h#".
				// Instead they start with "<number1> <number2> h#", where number1 > 0.
				// We override number1 with 0. */

				if (xmin > 0 && phnFile) {
					xmin = 0;
				}
			}
			TextInterval interval = (TextInterval) timit -> intervals -> item[ni];
			if (xmin < interval -> xmax && linesRead > 1) {
				xmin = interval -> xmax;
				Melder_warning (U"File \"", MelderFile_messageName (file), U"\": Start time set to previous end "
				                 U"time for label at line ", linesRead, U".");
			}
			// standard: new TextInterval
			const char *labelstring = (strncmp (label, "h#", 2) ? label : TIMIT_DELIMITER);
			IntervalTier_add (timit, xmin, xmax, Melder_peek8to32 (labelstring));
		}

		// Now correct the end times, based on last read interval.
		// (end time was set to large value!)

		if (timit -> intervals -> size < 2) {
			Melder_throw (U"Empty TextGrid");
		}
		Collection_removeItem (timit -> intervals, timit -> intervals -> size);
		TextInterval interval = (TextInterval) timit -> intervals -> item[timit -> intervals -> size];
		timit -> xmax = interval -> xmax;
		my xmax = xmax;
		if (phnFile) { // Create tier 2 with IPA symbols
			autoIntervalTier ipa = Data_copy (timit);
			Thing_setName (ipa.peek(), U"ipa");
			// First change the data in ipa
			for (long i = 1; i <= ipa -> intervals -> size; i++) {
				interval = (TextInterval) timit -> intervals -> item[i];

				TextInterval_setText ( (TextInterval) ipa -> intervals -> item[i],
				                       Melder_peek8to32 (timitLabelToIpaLabel (Melder_peek32to8 (interval -> text))));
			}
			Collection_addItem (my tiers, ipa.transfer()); // Then: add to collection
			Thing_setName (timit, U"phn");  // rename wrd
		}
		f.close (file);
		return me.transfer();
	} catch (MelderError) {
		Melder_throw (U"TextGrid not read from file ", file, U".");
	}
}
コード例 #3
0
ファイル: Intensity_extensions.cpp プロジェクト: psibre/praat
TextGrid Intensity_to_TextGrid_detectSilences (Intensity me, double silenceThreshold_dB,
		double minSilenceDuration, double minSoundingDuration, const char32 *silenceLabel, const char32 *soundingLabel) {
	try {
		double duration = my xmax - my xmin, time;

		if (silenceThreshold_dB >= 0) {
			Melder_throw (U"The silence threshold w.r.t. the maximum intensity should be a negative number.");
		}

		autoTextGrid thee = TextGrid_create (my xmin, my xmax, U"silences", U"");
		IntervalTier it = (IntervalTier) thy tiers -> item[1];
		TextInterval_setText ( (TextInterval) it -> intervals -> item[1], soundingLabel);
		if (minSilenceDuration > duration) {
			return thee.transfer();
		}

		double intensity_max_db, intensity_min_db, xOfMaximum, xOfMinimum;
		Vector_getMaximumAndX (me, 0, 0, 1, NUM_PEAK_INTERPOLATE_PARABOLIC, &intensity_max_db, &xOfMaximum);
		Vector_getMinimumAndX (me, 0, 0, 1, NUM_PEAK_INTERPOLATE_PARABOLIC, &intensity_min_db, &xOfMinimum);
		double intensity_dbRange = intensity_max_db - intensity_min_db;

		if (intensity_dbRange < 10) Melder_warning (U"The loudest and softest part in your sound only differ by ",
			        intensity_dbRange, U" dB.");

		double intensityThreshold = intensity_max_db - fabs (silenceThreshold_dB);

		if (minSilenceDuration > duration || intensityThreshold < intensity_min_db) {
			return thee.transfer();
		}

		int inSilenceInterval = my z[1][1] < intensityThreshold;
		long iinterval = 1;
		const char32 *label;
		for (long i = 2; i <= my nx; i++) {
			int addBoundary = 0;
			if (my z[1][i] < intensityThreshold) {
				if (!inSilenceInterval) { // Start of silence
					addBoundary = 1;
					inSilenceInterval = 1;
					label = soundingLabel;
				}
			} else {
				if (inSilenceInterval) { // End of silence
					addBoundary = 1;
					inSilenceInterval = 0;
					label = silenceLabel;
				}
			}

			if (addBoundary) {
				time = my x1 + (i - 1) * my dx;
				IntervalTier_addBoundaryUnsorted (it, iinterval, time, label);
				iinterval++;
			}
		}

		// (re)label last interval */

		label = inSilenceInterval ? silenceLabel : soundingLabel;
		TextInterval_setText ( (TextInterval) it -> intervals -> item[iinterval], label);
		Sorted_sort (it -> intervals);

		// First remove short non-silence intervals in-between silence intervals and
		// then remove the remaining short silence intervals.
		// This works much better than first removing short silence intervals and
		// then short non-silence intervals.

		IntervalTier_cutIntervals_minimumDuration (it, soundingLabel, minSoundingDuration);
		IntervalTier_cutIntervalsOnLabelMatch (it, silenceLabel);
		IntervalTier_cutIntervals_minimumDuration (it, silenceLabel, minSilenceDuration);
		IntervalTier_cutIntervalsOnLabelMatch (it, soundingLabel);

		return thee.transfer();
	} catch (MelderError) {
		Melder_throw (me, U": TextGrid not created.");
	}
}
コード例 #4
0
static autoTextGrid Table_to_TextGrid (Table me, const char32 *text, double xmin, double xmax) {
	//Table_createWithColumnNames (0, L"time type type-t t-pos length a-pos sample id uniq");
	try {
		long length, textLength = str32len (text);
		long numberOfRows = my rows.size;
		long timeColumnIndex = Table_getColumnIndexFromColumnLabel (me, U"time");
		long typeColumnIndex = Table_getColumnIndexFromColumnLabel (me, U"type");
		long tposColumnIndex = Table_getColumnIndexFromColumnLabel (me, U"t-pos");
		long   idColumnIndex = Table_getColumnIndexFromColumnLabel (me, U"id");
		autoTextGrid thee = TextGrid_create (xmin, xmax, U"sentence clause word phoneme", U"");

		TextGrid_setIntervalText (thee.get(), 1, 1, text);

		long p1c = 1, p1w = 1;
		double time_phon_p = xmin;
		bool wordEnd = false;
		autoMelderString mark;

		IntervalTier clauses = (IntervalTier) thy tiers->at [2];
		IntervalTier words = (IntervalTier) thy tiers->at [3];
		IntervalTier phonemes = (IntervalTier) thy tiers->at [4];

		for (long i = 1; i <= numberOfRows; i++) {
			double time = Table_getNumericValue_Assert (me, i, timeColumnIndex);
			int type = Table_getNumericValue_Assert (me, i, typeColumnIndex);
			long pos = Table_getNumericValue_Assert (me, i, tposColumnIndex);
			if (type == espeakEVENT_SENTENCE) {
				// Only insert a new boundary, no text
				// text will be inserted at end sentence event
				if (time > xmin && time < xmax) {
					IntervalTier_addBoundaryUnsorted (clauses, clauses -> intervals.size, time, U"", true);
				}
				p1c = pos;
			} else if (type == espeakEVENT_END) {
				// End of clause: insert new boundary, and fill left interval with text
				length = pos - p1c + 1;
				MelderString_ncopy (&mark, text + p1c - 1, length);
				MelderString_trimWhiteSpaceAtEnd (& mark);
				if (time > xmin && time < xmax) {
					IntervalTier_addBoundaryUnsorted (clauses, clauses -> intervals.size, time, mark.string, true);
				} else {
					TextGrid_setIntervalText (thee.get(), 2, clauses -> intervals.size, mark.string);
				}
				p1c = pos;

				// End of clause always signals "end of a word"

				if (pos <= textLength) {
					length = pos - p1w + 1;
					MelderString_ncopy (&mark, text + p1w - 1, length);
					MelderString_trimWhiteSpaceAtEnd (& mark);
					if (time > xmin && time < xmax) {
						IntervalTier_addBoundaryUnsorted (words, words -> intervals.size, time, mark.string, true);
					} else {
						TextGrid_setIntervalText (thee.get(), 3, words -> intervals.size, mark.string);
					}
					// now the next word event should not trigger setting the left interval text
					wordEnd = false;
				}
			} else if (type == espeakEVENT_WORD) {
				if (pos < p1w) {
					continue;
				}
				if (time > xmin && time < xmax) {
					length = pos - p1w;
					if (pos == textLength) {
						length++;
					}
					MelderString_ncopy (&mark, text + p1w - 1, length);
					MelderString_trimWhiteSpaceAtEnd (& mark);
					IntervalTier_addBoundaryUnsorted (words, words -> intervals.size, time, ( wordEnd ? mark.string : U"" ), true);
				}
				wordEnd = true;
				p1w = pos;
			} else if (type == espeakEVENT_PHONEME) {
				const char32 *id = Table_getStringValue_Assert (me, i, idColumnIndex);
				if (time > time_phon_p) {
					// Insert new boudary and label interval with the id
					// TODO: Translate the id to the correct notation
					TextInterval ti = phonemes -> intervals.at [phonemes -> intervals.size];
					if (time > ti -> xmin && time < ti -> xmax) {
						IntervalTier_addBoundaryUnsorted (phonemes, phonemes -> intervals.size, time, id, false);
					}
				} else {
					// Just in case the phoneme starts at xmin we only need to set interval text
					TextGrid_setIntervalText (thee.get(), 4, phonemes -> intervals.size, id);
				}
				time_phon_p = time;
			}
		}
		clauses -> intervals. sort ();
		words -> intervals. sort ();
		phonemes -> intervals. sort ();
		
		IntervalTier_mergeSpecialIntervals (phonemes); // Merge neighbouring empty U"" and U"\001" intervals
		
		IntervalTier_removeVeryShortIntervals (words);
		IntervalTier_removeVeryShortIntervals (clauses);
		
		/* Use empty intervals in phoneme tier for more precision in the word tier */
		
		IntervalTier_insertEmptyIntervalsFromOtherTier (words, phonemes);
		IntervalTier_mergeSpecialIntervals (words); // Merge neighbouring empty U"" and U"\001" intervals

		return thee;
	} catch (MelderError) {
		Melder_throw (U"TextGrid not created from Table with events.");
	}
}
コード例 #5
0
ファイル: SpeechSynthesizer.cpp プロジェクト: ffostertw/praat
static autoTextGrid Table_to_TextGrid (Table me, const char32 *text, double xmin, double xmax) {
	//Table_createWithColumnNames (0, L"time type type-t t-pos length a-pos sample id uniq");
	try {
		long length, textLength = str32len (text);
		long numberOfRows = my rows -> size;
		long timeColumnIndex = Table_getColumnIndexFromColumnLabel (me, U"time");
		long typeColumnIndex = Table_getColumnIndexFromColumnLabel (me, U"type");
		long tposColumnIndex = Table_getColumnIndexFromColumnLabel (me, U"t-pos");
		long   idColumnIndex = Table_getColumnIndexFromColumnLabel (me, U"id");
		autoTextGrid thee = TextGrid_create (xmin, xmax, U"sentence clause word phoneme", U"");

		TextGrid_setIntervalText (thee.peek(), 1, 1, text);

		long p1c = 1, p1w = 1;
		double t1p = xmin;
		bool wordEnd = false;
		autoMelderString mark;

		IntervalTier itc = (IntervalTier) thy tiers -> item[2];
		IntervalTier itw = (IntervalTier) thy tiers -> item[3];
		IntervalTier itp = (IntervalTier) thy tiers -> item[4];

		for (long i = 1; i <= numberOfRows; i++) {
			double time = Table_getNumericValue_Assert (me, i, timeColumnIndex);
			int type = Table_getNumericValue_Assert (me, i, typeColumnIndex);
			long pos = Table_getNumericValue_Assert (me, i, tposColumnIndex);
			if (type == espeakEVENT_SENTENCE) {
				// Only insert a new boundary, no text
				// text will be inserted at end sentence event
				if (time > xmin and time < xmax) {
					IntervalTier_addBoundaryUnsorted (itc, itc -> intervals -> size, time, U"", true);
				}
				p1c = pos;
			} else if (type == espeakEVENT_END) {
				// End of clause: insert new boundary, and fill left interval with text
				length = pos - p1c + 1;
				MelderString_ncopy (&mark, text + p1c - 1, length);
				MelderString_trimWhiteSpaceAtEnd (&mark);
				if (time > xmin and time < xmax) {
					IntervalTier_addBoundaryUnsorted (itc, itc -> intervals -> size, time, mark.string, true);
				} else {
					TextGrid_setIntervalText (thee.peek(), 2, itc -> intervals -> size, mark.string);
				}
				p1c = pos;

				// End of clause always signals "end of a word"

				if (pos <= textLength) {
					length = pos - p1w + 1;
					MelderString_ncopy (&mark, text + p1w - 1, length);
					MelderString_trimWhiteSpaceAtEnd (&mark);
					if (time > xmin and time < xmax) {
						IntervalTier_addBoundaryUnsorted (itw, itw -> intervals -> size, time, mark.string, true);
					} else {
						TextGrid_setIntervalText (thee.peek(), 3, itw -> intervals -> size, mark.string);
					}
					// now the next word event should not trigger setting the left interval text
					wordEnd = false;
				}
			} else if (type == espeakEVENT_WORD) {
				if (pos < p1w) {
					continue;
				}
				if (time > xmin and time < xmax) {
					length = pos - p1w;
					if (pos == textLength) length++;
					MelderString_ncopy (&mark, text + p1w - 1, length);
					MelderString_trimWhiteSpaceAtEnd (&mark);
					IntervalTier_addBoundaryUnsorted (itw, itw -> intervals -> size, time, (wordEnd ? mark.string : U""), true);
				}
				wordEnd = true;
				p1w = pos;
			} else if (type == espeakEVENT_PHONEME) {
				const char32 *id = Table_getStringValue_Assert (me, i, idColumnIndex);
				if (time > t1p) {
					// Insert new boudary and label interval with the id
					// TODO: Translate the id to the correct notation
					TextInterval ti = (TextInterval) itp -> intervals -> item[itp -> intervals -> size];
					if (time > ti -> xmin and time < ti -> xmax) {
						IntervalTier_addBoundaryUnsorted (itp, itp -> intervals -> size, time, id, false);
					}
				} else {
					// Just in case the phoneme starts at xmin we only need to set interval text
					TextGrid_setIntervalText (thee.peek(), 4, itp -> intervals -> size, id);
				}
				t1p = time;
			}
		}
		Sorted_sort (itc -> intervals);
		Sorted_sort (itw -> intervals);
		Sorted_sort (itp -> intervals);
		return thee;
	} catch (MelderError) {
		Melder_throw (U"TextGrid not created from Table with events.");
	}
}
コード例 #6
0
ファイル: EEG.cpp プロジェクト: guilhermegarcia/praat-1
autoEEG EEG_readFromBdfFile (MelderFile file) {
	try {
		autofile f = Melder_fopen (file, "rb");
		char buffer [81];
		fread (buffer, 1, 8, f); buffer [8] = '\0';
		bool is24bit = buffer [0] == (char) 255;
		fread (buffer, 1, 80, f); buffer [80] = '\0';
		trace (U"Local subject identification: \"", Melder_peek8to32 (buffer), U"\"");
		fread (buffer, 1, 80, f); buffer [80] = '\0';
		trace (U"Local recording identification: \"", Melder_peek8to32 (buffer), U"\"");
		fread (buffer, 1, 8, f); buffer [8] = '\0';
		trace (U"Start date of recording: \"", Melder_peek8to32 (buffer), U"\"");
		fread (buffer, 1, 8, f); buffer [8] = '\0';
		trace (U"Start time of recording: \"", Melder_peek8to32 (buffer), U"\"");
		fread (buffer, 1, 8, f); buffer [8] = '\0';
		long numberOfBytesInHeaderRecord = atol (buffer);
		trace (U"Number of bytes in header record: ", numberOfBytesInHeaderRecord);
		fread (buffer, 1, 44, f); buffer [44] = '\0';
		trace (U"Version of data format: \"", Melder_peek8to32 (buffer), U"\"");
		fread (buffer, 1, 8, f); buffer [8] = '\0';
		long numberOfDataRecords = strtol (buffer, nullptr, 10);
		trace (U"Number of data records: ", numberOfDataRecords);
		fread (buffer, 1, 8, f); buffer [8] = '\0';
		double durationOfDataRecord = atof (buffer);
		trace (U"Duration of a data record: ", durationOfDataRecord);
		fread (buffer, 1, 4, f); buffer [4] = '\0';
		long numberOfChannels = atol (buffer);
		trace (U"Number of channels in data record: ", numberOfChannels);
		if (numberOfBytesInHeaderRecord != (numberOfChannels + 1) * 256)
			Melder_throw (U"Number of bytes in header record (", numberOfBytesInHeaderRecord,
				U") doesn't match number of channels (", numberOfChannels, U").");
		autostring32vector channelNames (1, numberOfChannels);
		for (long ichannel = 1; ichannel <= numberOfChannels; ichannel ++) {
			fread (buffer, 1, 16, f); buffer [16] = '\0';   // labels of the channels
			/*
			 * Strip all final spaces.
			 */
			for (int i = 15; i >= 0; i --) {
				if (buffer [i] == ' ') {
					buffer [i] = '\0';
				} else {
					break;
				}
			}
			channelNames [ichannel] = Melder_8to32 (buffer);
			trace (U"Channel <<", channelNames [ichannel], U">>");
		}
		bool hasLetters = str32equ (channelNames [numberOfChannels], U"EDF Annotations");
		double samplingFrequency = NUMundefined;
		for (long channel = 1; channel <= numberOfChannels; channel ++) {
			fread (buffer, 1, 80, f); buffer [80] = '\0';   // transducer type
		}
		for (long channel = 1; channel <= numberOfChannels; channel ++) {
			fread (buffer, 1, 8, f); buffer [8] = '\0';   // physical dimension of channels
		}
		autoNUMvector <double> physicalMinimum (1, numberOfChannels);
		for (long ichannel = 1; ichannel <= numberOfChannels; ichannel ++) {
			fread (buffer, 1, 8, f); buffer [8] = '\0';
			physicalMinimum [ichannel] = atof (buffer);
		}
		autoNUMvector <double> physicalMaximum (1, numberOfChannels);
		for (long ichannel = 1; ichannel <= numberOfChannels; ichannel ++) {
			fread (buffer, 1, 8, f); buffer [8] = '\0';
			physicalMaximum [ichannel] = atof (buffer);
		}
		autoNUMvector <double> digitalMinimum (1, numberOfChannels);
		for (long ichannel = 1; ichannel <= numberOfChannels; ichannel ++) {
			fread (buffer, 1, 8, f); buffer [8] = '\0';
			digitalMinimum [ichannel] = atof (buffer);
		}
		autoNUMvector <double> digitalMaximum (1, numberOfChannels);
		for (long ichannel = 1; ichannel <= numberOfChannels; ichannel ++) {
			fread (buffer, 1, 8, f); buffer [8] = '\0';
			digitalMaximum [ichannel] = atof (buffer);
		}
		for (long channel = 1; channel <= numberOfChannels; channel ++) {
			fread (buffer, 1, 80, f); buffer [80] = '\0';   // prefiltering
		}
		long numberOfSamplesPerDataRecord = 0;
		for (long channel = 1; channel <= numberOfChannels; channel ++) {
			fread (buffer, 1, 8, f); buffer [8] = '\0';   // number of samples in each data record
			long numberOfSamplesInThisDataRecord = atol (buffer);
			if (samplingFrequency == NUMundefined) {
				numberOfSamplesPerDataRecord = numberOfSamplesInThisDataRecord;
				samplingFrequency = numberOfSamplesInThisDataRecord / durationOfDataRecord;
			}
			if (numberOfSamplesInThisDataRecord / durationOfDataRecord != samplingFrequency)
				Melder_throw (U"Number of samples per data record in channel ", channel,
					U" (", numberOfSamplesInThisDataRecord,
					U") doesn't match sampling frequency of channel 1 (", samplingFrequency, U").");
		}
		for (long channel = 1; channel <= numberOfChannels; channel ++) {
			fread (buffer, 1, 32, f); buffer [32] = '\0';   // reserved
		}
		double duration = numberOfDataRecords * durationOfDataRecord;
		autoEEG him = EEG_create (0, duration);
		his numberOfChannels = numberOfChannels;
		autoSound me = Sound_createSimple (numberOfChannels, duration, samplingFrequency);
		Melder_assert (my nx == numberOfSamplesPerDataRecord * numberOfDataRecords);
		autoNUMvector <unsigned char> dataBuffer (0L, 3 * numberOfSamplesPerDataRecord - 1);
		for (long record = 1; record <= numberOfDataRecords; record ++) {
			for (long channel = 1; channel <= numberOfChannels; channel ++) {
				double factor = channel == numberOfChannels ? 1.0 : physicalMinimum [channel] / digitalMinimum [channel];
				if (channel < numberOfChannels - EEG_getNumberOfExtraSensors (him.peek())) factor /= 1000000.0;
				if (is24bit) {
					fread (& dataBuffer [0], 3, numberOfSamplesPerDataRecord, f);
					unsigned char *p = & dataBuffer [0];
					for (long i = 1; i <= numberOfSamplesPerDataRecord; i ++) {
						long sample = i + (record - 1) * numberOfSamplesPerDataRecord;
						Melder_assert (sample <= my nx);
						uint8_t lowByte = *p ++, midByte = *p ++, highByte = *p ++;
						uint32_t externalValue = ((uint32_t) highByte << 16) | ((uint32_t) midByte << 8) | (uint32_t) lowByte;
						if ((highByte & 128) != 0)   // is the 24-bit sign bit on?
							externalValue |= 0xFF000000;   // extend negative sign to 32 bits
						my z [channel] [sample] = (int32_t) externalValue * factor;
					}
				} else {
					fread (& dataBuffer [0], 2, numberOfSamplesPerDataRecord, f);
					unsigned char *p = & dataBuffer [0];
					for (long i = 1; i <= numberOfSamplesPerDataRecord; i ++) {
						long sample = i + (record - 1) * numberOfSamplesPerDataRecord;
						Melder_assert (sample <= my nx);
						uint8 lowByte = *p ++, highByte = *p ++;
						uint16 externalValue = (uint16) ((uint16) highByte << 8) | (uint16) lowByte;
						my z [channel] [sample] = (int16) externalValue * factor;
					}
				}
			}
		}
		int numberOfStatusBits = 8;
		for (long i = 1; i <= my nx; i ++) {
			unsigned long value = (long) my z [numberOfChannels] [i];
			if (value & 0x0000FF00) {
				numberOfStatusBits = 16;
			}
		}
		autoTextGrid thee;
		if (hasLetters) {
			thee = TextGrid_create (0, duration, U"Mark Trigger", U"Mark Trigger");
			autoMelderString letters;
			double time = NUMundefined;
			for (long i = 1; i <= my nx; i ++) {
				unsigned long value = (long) my z [numberOfChannels] [i];
				for (int byte = 1; byte <= numberOfStatusBits / 8; byte ++) {
					unsigned long mask = byte == 1 ? 0x000000ff : 0x0000ff00;
					char32 kar = byte == 1 ? (value & mask) : (value & mask) >> 8;
					if (kar != U'\0' && kar != 20) {
						MelderString_appendCharacter (& letters, kar);
					} else if (letters. string [0] != U'\0') {
						if (letters. string [0] == U'+') {
							if (NUMdefined (time)) {
								try {
									TextGrid_insertPoint (thee.peek(), 1, time, U"");
								} catch (MelderError) {
									Melder_throw (U"Did not insert empty mark (", letters. string, U") on Mark tier.");
								}
								time = NUMundefined;   // defensive
							}
							time = Melder_atof (& letters. string [1]);
							MelderString_empty (& letters);
						} else {
							if (! NUMdefined (time)) {
								Melder_throw (U"Undefined time for label at sample ", i, U".");
							}
							try {
								if (Melder_nequ (letters. string, U"Trigger-", 8)) {
									try {
										TextGrid_insertPoint (thee.peek(), 2, time, & letters. string [8]);
									} catch (MelderError) {
										Melder_clearError ();
										trace (U"Duplicate trigger at ", time, U" seconds: ", & letters. string [8]);
									}
								} else {
									TextGrid_insertPoint (thee.peek(), 1, time, & letters. string [0]);
								}
							} catch (MelderError) {
								Melder_throw (U"Did not insert mark (", letters. string, U") on Trigger tier.");
							}
							time = NUMundefined;   // crucial
							MelderString_empty (& letters);
						}
					}
				}
			}
			if (NUMdefined (time)) {
				TextGrid_insertPoint (thee.peek(), 1, time, U"");
				time = NUMundefined;   // defensive
			}
		} else {
			thee = TextGrid_create (0, duration,
				numberOfStatusBits == 8 ? U"S1 S2 S3 S4 S5 S6 S7 S8" : U"S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 S13 S14 S15 S16", U"");
			for (int bit = 1; bit <= numberOfStatusBits; bit ++) {
				unsigned long bitValue = 1 << (bit - 1);
				IntervalTier tier = (IntervalTier) thy tiers -> item [bit];
				for (long i = 1; i <= my nx; i ++) {
					unsigned long previousValue = i == 1 ? 0 : (long) my z [numberOfChannels] [i - 1];
					unsigned long thisValue = (long) my z [numberOfChannels] [i];
					if ((thisValue & bitValue) != (previousValue & bitValue)) {
						double time = i == 1 ? 0.0 : my x1 + (i - 1.5) * my dx;
						if (time != 0.0)
							TextGrid_insertBoundary (thee.peek(), bit, time);
						if ((thisValue & bitValue) != 0)
							TextGrid_setIntervalText (thee.peek(), bit, tier -> intervals -> size, U"1");
					}
				}
			}
		}
		f.close (file);
		his channelNames = channelNames.transfer();
		his sound = me.move();
		his textgrid = thee.move();
		if (EEG_getNumberOfCapElectrodes (him.peek()) == 32) {
			EEG_setChannelName (him.peek(), 1, U"Fp1");
			EEG_setChannelName (him.peek(), 2, U"AF3");
			EEG_setChannelName (him.peek(), 3, U"F7");
			EEG_setChannelName (him.peek(), 4, U"F3");
			EEG_setChannelName (him.peek(), 5, U"FC1");
			EEG_setChannelName (him.peek(), 6, U"FC5");
			EEG_setChannelName (him.peek(), 7, U"T7");
			EEG_setChannelName (him.peek(), 8, U"C3");
			EEG_setChannelName (him.peek(), 9, U"CP1");
			EEG_setChannelName (him.peek(), 10, U"CP5");
			EEG_setChannelName (him.peek(), 11, U"P7");
			EEG_setChannelName (him.peek(), 12, U"P3");
			EEG_setChannelName (him.peek(), 13, U"Pz");
			EEG_setChannelName (him.peek(), 14, U"PO3");
			EEG_setChannelName (him.peek(), 15, U"O1");
			EEG_setChannelName (him.peek(), 16, U"Oz");
			EEG_setChannelName (him.peek(), 17, U"O2");
			EEG_setChannelName (him.peek(), 18, U"PO4");
			EEG_setChannelName (him.peek(), 19, U"P4");
			EEG_setChannelName (him.peek(), 20, U"P8");
			EEG_setChannelName (him.peek(), 21, U"CP6");
			EEG_setChannelName (him.peek(), 22, U"CP2");
			EEG_setChannelName (him.peek(), 23, U"C4");
			EEG_setChannelName (him.peek(), 24, U"T8");
			EEG_setChannelName (him.peek(), 25, U"FC6");
			EEG_setChannelName (him.peek(), 26, U"FC2");
			EEG_setChannelName (him.peek(), 27, U"F4");
			EEG_setChannelName (him.peek(), 28, U"F8");
			EEG_setChannelName (him.peek(), 29, U"AF4");
			EEG_setChannelName (him.peek(), 30, U"Fp2");
			EEG_setChannelName (him.peek(), 31, U"Fz");
			EEG_setChannelName (him.peek(), 32, U"Cz");
		} else if (EEG_getNumberOfCapElectrodes (him.peek()) == 64) {
			EEG_setChannelName (him.peek(), 1, U"Fp1");
			EEG_setChannelName (him.peek(), 2, U"AF7");
			EEG_setChannelName (him.peek(), 3, U"AF3");
			EEG_setChannelName (him.peek(), 4, U"F1");
			EEG_setChannelName (him.peek(), 5, U"F3");
			EEG_setChannelName (him.peek(), 6, U"F5");
			EEG_setChannelName (him.peek(), 7, U"F7");
			EEG_setChannelName (him.peek(), 8, U"FT7");
			EEG_setChannelName (him.peek(), 9, U"FC5");
			EEG_setChannelName (him.peek(), 10, U"FC3");
			EEG_setChannelName (him.peek(), 11, U"FC1");
			EEG_setChannelName (him.peek(), 12, U"C1");
			EEG_setChannelName (him.peek(), 13, U"C3");
			EEG_setChannelName (him.peek(), 14, U"C5");
			EEG_setChannelName (him.peek(), 15, U"T7");
			EEG_setChannelName (him.peek(), 16, U"TP7");
			EEG_setChannelName (him.peek(), 17, U"CP5");
			EEG_setChannelName (him.peek(), 18, U"CP3");
			EEG_setChannelName (him.peek(), 19, U"CP1");
			EEG_setChannelName (him.peek(), 20, U"P1");
			EEG_setChannelName (him.peek(), 21, U"P3");
			EEG_setChannelName (him.peek(), 22, U"P5");
			EEG_setChannelName (him.peek(), 23, U"P7");
			EEG_setChannelName (him.peek(), 24, U"P9");
			EEG_setChannelName (him.peek(), 25, U"PO7");
			EEG_setChannelName (him.peek(), 26, U"PO3");
			EEG_setChannelName (him.peek(), 27, U"O1");
			EEG_setChannelName (him.peek(), 28, U"Iz");
			EEG_setChannelName (him.peek(), 29, U"Oz");
			EEG_setChannelName (him.peek(), 30, U"POz");
			EEG_setChannelName (him.peek(), 31, U"Pz");
			EEG_setChannelName (him.peek(), 32, U"CPz");
			EEG_setChannelName (him.peek(), 33, U"Fpz");
			EEG_setChannelName (him.peek(), 34, U"Fp2");
			EEG_setChannelName (him.peek(), 35, U"AF8");
			EEG_setChannelName (him.peek(), 36, U"AF4");
			EEG_setChannelName (him.peek(), 37, U"AFz");
			EEG_setChannelName (him.peek(), 38, U"Fz");
			EEG_setChannelName (him.peek(), 39, U"F2");
			EEG_setChannelName (him.peek(), 40, U"F4");
			EEG_setChannelName (him.peek(), 41, U"F6");
			EEG_setChannelName (him.peek(), 42, U"F8");
			EEG_setChannelName (him.peek(), 43, U"FT8");
			EEG_setChannelName (him.peek(), 44, U"FC6");
			EEG_setChannelName (him.peek(), 45, U"FC4");
			EEG_setChannelName (him.peek(), 46, U"FC2");
			EEG_setChannelName (him.peek(), 47, U"FCz");
			EEG_setChannelName (him.peek(), 48, U"Cz");
			EEG_setChannelName (him.peek(), 49, U"C2");
			EEG_setChannelName (him.peek(), 50, U"C4");
			EEG_setChannelName (him.peek(), 51, U"C6");
			EEG_setChannelName (him.peek(), 52, U"T8");
			EEG_setChannelName (him.peek(), 53, U"TP8");
			EEG_setChannelName (him.peek(), 54, U"CP6");
			EEG_setChannelName (him.peek(), 55, U"CP4");
			EEG_setChannelName (him.peek(), 56, U"CP2");
			EEG_setChannelName (him.peek(), 57, U"P2");
			EEG_setChannelName (him.peek(), 58, U"P4");
			EEG_setChannelName (him.peek(), 59, U"P6");
			EEG_setChannelName (him.peek(), 60, U"P8");
			EEG_setChannelName (him.peek(), 61, U"P10");
			EEG_setChannelName (him.peek(), 62, U"PO8");
			EEG_setChannelName (him.peek(), 63, U"PO4");
			EEG_setChannelName (him.peek(), 64, U"O2");
		}
		return him;
	} catch (MelderError) {